Giao diện
Classes & Objects trong Python
Khi bạn viết models.py trong Django, khai báo schema Pydantic cho FastAPI, hay map bảng SQL qua SQLAlchemy — tất cả đều xoay quanh class. Một model User chứa tên, email, logic validate password; một schema OrderResponse định nghĩa kiểu dữ liệu trả về API. Nếu không hiểu class hoạt động ra sao bên dưới, bạn sẽ liên tục gặp bug khó giải thích: attribute bị chia sẻ giữa các instance, __init__ gọi sai thứ tự, hay mutable default âm thầm phá dữ liệu.
Có một sự thật ít người để ý: mọi thứ trong Python đều là object. Số 42 là instance của int, chuỗi "hello" là instance của str, thậm chí hàm print cũng là object có __class__. Class chỉ là bản thiết kế để tạo ra các object đó — và bạn hoàn toàn có thể tự viết bản thiết kế của riêng mình.
Trang này sẽ đưa bạn từ cú pháp cơ bản đến hiểu sâu cách Python quản lý object trong bộ nhớ, kèm theo pattern @dataclass hiện đại mà production code ngày nay sử dụng rộng rãi.
Bức tranh tư duy
Hãy tưởng tượng bạn là kiến trúc sư. Class giống như bản thiết kế ngôi nhà: nó mô tả có bao nhiêu phòng, cửa sổ đặt ở đâu, hệ thống điện nước chạy thế nào. Bản thân bản thiết kế không phải là nhà — bạn không thể ở trong một tờ giấy A0. Nhưng từ một bản thiết kế, bạn có thể xây nhiều ngôi nhà thực tế (object/instance), mỗi ngôi nhà có màu sơn khác nhau, nội thất khác nhau, người ở khác nhau.
┌─────────────────────────────────┐
│ CLASS (Blueprint) │
│ ┌───────────────────────────┐ │
│ │ attributes: ten, email │ │
│ │ methods: chao() │ │
│ └───────────────────────────┘ │
└──────────────┬──────────────────┘
│ __init__()
┌───────┼───────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│ obj_1 │ │ obj_2 │ │ obj_3 │
│ten="A"│ │ten="B"│ │ten="C"│
│email= │ │email= │ │email= │
│"a@x" │ │"b@x" │ │"c@x" │
└───────┘ └───────┘ └───────┘
Instance Instance InstanceTương tự trong phần mềm: class User là bản thiết kế, user_1 = User("An", "an@x.vn") là instance cụ thể có dữ liệu thật.
Khi analogy gãy: Khác với nhà thật, Python object có thể thay đổi cấu trúc sau khi tạo — gắn attribute mới bất cứ lúc nào (user_1.tuoi = 25). Đây là bản chất dynamic của Python — linh hoạt nhưng dễ gây bug nếu không cẩn thận.
Cốt lõi kỹ thuật
Cấu trúc cơ bản của class
Mọi class trong Python bắt đầu bằng keyword class, theo sau là tên class viết PascalCase, và kết thúc bằng dấu :. Bên trong là docstring, attributes, và methods.
python
class User:
"""Đại diện một người dùng trong hệ thống.
Attributes:
platform: Tên nền tảng (class attribute, chung cho mọi instance).
ten: Tên người dùng.
email: Địa chỉ email.
"""
# Class attribute — chia sẻ bởi tất cả instances
platform: str = "PENALGO"
def __init__(self, ten: str, email: str) -> None:
# Instance attributes — riêng biệt cho từng instance
self.ten = ten
self.email = email
def chao(self) -> str:
"""Trả về lời chào cá nhân hoá."""
return f"Xin chào, tôi là {self.ten} trên {self.platform}"
# Tạo hai instance từ cùng một class
user_a = User("An", "an@company.vn")
user_b = User("Bình", "binh@company.vn")
print(user_a.chao()) # Xin chào, tôi là An trên PENALGO
print(user_b.chao()) # Xin chào, tôi là Bình trên PENALGO
print(type(user_a)) # <class '__main__.User'>Ba thành phần cốt lõi: class attribute (platform) thuộc về class, instance attribute (self.ten) thuộc về từng object, method (chao) nhận instance qua self.
Phương thức __init__ và __new__
Nhiều lập trình viên gọi __init__ là "constructor", nhưng thực tế Python tách quá trình tạo object thành hai bước riêng biệt:
| Bước | Method | Vai trò |
|---|---|---|
| 1 | __new__ | Allocator — tạo ra instance rỗng trong bộ nhớ |
| 2 | __init__ | Initializer — gán dữ liệu vào instance đã tạo |
python
class Demo:
def __new__(cls, *args, **kwargs):
print(f"1. __new__ được gọi — tạo instance của {cls.__name__}")
instance = super().__new__(cls)
return instance
def __init__(self, gia_tri: int) -> None:
print(f"2. __init__ được gọi — gán gia_tri = {gia_tri}")
self.gia_tri = gia_tri
obj = Demo(42)
# Output:
# 1. __new__ được gọi — tạo instance của Demo
# 2. __init__ được gọi — gán gia_tri = 42Khi nào cần override __new__? Rất hiếm — chủ yếu Singleton pattern, kế thừa immutable types (int, str, tuple), hoặc metaclass. Trong 99% trường hợp production, chỉ cần __init__.
python
class Singleton:
"""Pattern Singleton dùng __new__."""
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name: str = "default") -> None:
self.name = name
a = Singleton("first")
b = Singleton("second")
print(a is b) # True — cùng một object
print(a.name) # "second" — __init__ vẫn chạy lại!self — tham chiếu tường minh
Python bắt buộc khai báo self làm tham số đầu tiên của mọi instance method — quyết định thiết kế có chủ đích, khác với Java/C# dùng this ngầm định.
python
class Counter:
def __init__(self) -> None:
self.count = 0
def increment(self) -> None:
self.count += 1
counter = Counter()
# Hai cách gọi tương đương:
counter.increment() # Python tự truyền counter → self
Counter.increment(counter) # Gọi tường minh — cùng kết quả
print(counter.count) # 1Khi bạn viết counter.increment(), Python thực chất dịch thành Counter.increment(counter). Tham số self là cách Python nói rõ: "biến này thuộc về instance, không phải biến local hay global".
python
class User:
def cap_nhat_ten(self, ten_moi: str) -> None:
self.ten = ten_moi # ← Rõ ràng: instance attribute
log_msg = "Đã cập nhật" # ← Rõ ràng: biến localSo với Java nơi name = newName có thể là instance variable, local variable, hay parameter — Python loại bỏ mọi sự mơ hồ. Triết lý "Explicit is better than implicit" (PEP 20) được thể hiện rõ nhất ở đây.
Instance attribute vs Class attribute
Đây là một trong những nguồn gây bug phổ biến nhất với người mới. Hai loại attribute này có cơ chế lưu trữ và lookup hoàn toàn khác nhau.
python
class NhanVien:
# Class attribute — lưu trong NhanVien.__dict__
cong_ty = "TechCorp"
so_nhan_vien = 0
def __init__(self, ten: str) -> None:
# Instance attribute — lưu trong self.__dict__
self.ten = ten
NhanVien.so_nhan_vien += 1
nv1 = NhanVien("An")
nv2 = NhanVien("Bình")
print(NhanVien.so_nhan_vien) # 2 — truy cập qua class
print(nv1.cong_ty) # "TechCorp" — fallback lên classChuỗi lookup khi truy cập attribute:
obj.attr
→ tìm trong obj.__dict__ (instance attributes)
→ tìm trong type(obj).__dict__ (class attributes)
→ tìm trong bases.__dict__ (parent classes, theo MRO)
→ raise AttributeErrorBẫy kinh điển: Dùng mutable (list, dict) làm class attribute sẽ chia sẻ giữa mọi instance — xem chi tiết ở phần Sai lầm điển hình.
@dataclass — OOP hiện đại
Từ Python 3.7, decorator @dataclass tự động sinh __init__, __repr__, __eq__ và nhiều method khác. Đây là cách viết class hiệu quả cho production code.
Trước — boilerplate thủ công (phải viết __init__, __repr__, __eq__ bằng tay). Sau — @dataclass làm tất cả:
python
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
quantity: int = 0
# Tự động có __init__, __repr__, __eq__
p = Product("Laptop", 25_000_000, quantity=3)
print(p) # Product(name='Laptop', price=25000000, quantity=3)field() và __post_init__ cho logic phức tạp:
python
from dataclasses import dataclass, field
from typing import ClassVar
@dataclass
class Order:
product_name: str
unit_price: float
quantity: int = 1
tags: list[str] = field(default_factory=list) # Mutable default an toàn
# Computed field — không nằm trong __init__
total: float = field(init=False)
# Class variable — không phải instance attribute
currency: ClassVar[str] = "VND"
def __post_init__(self) -> None:
"""Chạy ngay sau __init__ — nơi đặt validation và computed fields."""
if self.unit_price < 0:
raise ValueError(f"unit_price phải >= 0, nhận {self.unit_price}")
self.total = self.unit_price * self.quantity
order = Order("Bàn phím", 1_500_000, quantity=2)
print(order.total) # 3000000
print(Order.currency) # VNDfrozen=True — immutable dataclass:
python
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(10.0, 20.0)
p.x = 30.0 # ❌ FrozenInstanceError — không thể thay đổislots=True (Python 3.10+) — tối ưu bộ nhớ:
python
@dataclass(slots=True)
class SensorReading:
sensor_id: str
value: float
timestamp: float
# Dùng __slots__ thay vì __dict__
# → Tiết kiệm ~40% RAM khi có hàng triệu instanceThực chiến
Tình huống: Xây dựng lớp Product cho hệ thống e-commerce — cần validation, computed properties, và representation phù hợp cho debugging.
Cách 1 — Class truyền thống (kiểm soát hoàn toàn)
python
from __future__ import annotations
from decimal import Decimal
from enum import Enum
class ProductStatus(Enum):
ACTIVE = "active"
DRAFT = "draft"
ARCHIVED = "archived"
class Product:
"""Sản phẩm trong hệ thống e-commerce.
Validation chạy trong __init__ — fail fast, không để dữ liệu xấu
lan ra phần còn lại của hệ thống.
"""
# Class attribute — thuế VAT áp dụng cho mọi sản phẩm
VAT_RATE: Decimal = Decimal("0.08")
def __init__(
self,
sku: str,
name: str,
base_price: Decimal,
stock: int = 0,
status: ProductStatus = ProductStatus.DRAFT,
) -> None:
# --- Validation ---
if not sku or not sku.strip():
raise ValueError("SKU không được rỗng")
if base_price < 0:
raise ValueError(f"Giá phải >= 0, nhận {base_price}")
if stock < 0:
raise ValueError(f"Tồn kho phải >= 0, nhận {stock}")
# --- Assignment ---
self.sku = sku.strip().upper()
self.name = name.strip()
self.base_price = base_price
self.stock = stock
self.status = status
@property
def price_with_vat(self) -> Decimal:
"""Giá sau thuế — computed property, luôn đồng bộ với base_price."""
return self.base_price * (1 + self.VAT_RATE)
@property
def is_available(self) -> bool:
"""Sản phẩm có thể bán không."""
return self.status == ProductStatus.ACTIVE and self.stock > 0
def adjust_stock(self, delta: int) -> None:
"""Tăng/giảm tồn kho. Raise nếu kết quả âm."""
new_stock = self.stock + delta
if new_stock < 0:
raise ValueError(
f"Không đủ hàng: hiện {self.stock}, yêu cầu giảm {abs(delta)}"
)
self.stock = new_stock
def __repr__(self) -> str:
return (
f"Product(sku={self.sku!r}, name={self.name!r}, "
f"base_price={self.base_price}, stock={self.stock}, "
f"status={self.status.value!r})"
)
# --- Sử dụng ---
laptop = Product(
sku="LAP-001",
name="ThinkPad X1 Carbon",
base_price=Decimal("32_000_000"),
stock=15,
status=ProductStatus.ACTIVE,
)
print(laptop) # Product(sku='LAP-001', ...)
print(laptop.price_with_vat) # 34560000.00
print(laptop.is_available) # True
laptop.adjust_stock(-3)
print(laptop.stock) # 12Cách 2 — @dataclass với validation
python
from dataclasses import dataclass, field
from decimal import Decimal
# ProductStatus giữ nguyên như trên
@dataclass
class Product:
"""Phiên bản dataclass — ít boilerplate hơn, vẫn có validation."""
sku: str
name: str
base_price: Decimal
stock: int = 0
status: ProductStatus = ProductStatus.DRAFT
tags: list[str] = field(default_factory=list)
VAT_RATE: Decimal = field(
default=Decimal("0.08"), init=False, repr=False
)
def __post_init__(self) -> None:
if not self.sku or not self.sku.strip():
raise ValueError("SKU không được rỗng")
if self.base_price < 0:
raise ValueError(f"Giá phải >= 0, nhận {self.base_price}")
# Normalize sau validation
self.sku = self.sku.strip().upper()
self.name = self.name.strip()
@property
def price_with_vat(self) -> Decimal:
return self.base_price * (1 + self.VAT_RATE)
product = Product("kb-100", " Bàn phím cơ ", Decimal("2_500_000"))
print(product.sku) # "KB-100" — đã normalize
print(product.name) # "Bàn phím cơ" — đã stripTrade-off giữa hai cách:
| Tiêu chí | Class truyền thống | @dataclass |
|---|---|---|
| Boilerplate | Nhiều | Ít |
__repr__/__eq__ | Viết tay | Tự động sinh |
| Validation phức tạp | Linh hoạt trong __init__ | Dồn vào __post_init__ |
| Computed fields | @property — giống nhau | field(init=False) + __post_init__ |
| Immutability | Tự quản lý | frozen=True |
| Performance | Tương đương | slots=True cho lợi thế nhỏ |
Nguyên tắc thực tế: Nếu class chủ yếu chứa dữ liệu → @dataclass. Nếu class có hành vi phức tạp, lifecycle management, hay cần kiểm soát từng chi tiết khởi tạo → class truyền thống.
Sai lầm điển hình
❌ Sai lầm 1: Mutable default trong class attribute
python
# ❌ SAI — list chia sẻ giữa tất cả instances
class NotificationQueue:
messages = []
def add(self, msg: str) -> None:
self.messages.append(msg)
q1 = NotificationQueue()
q2 = NotificationQueue()
q1.add("Đơn hàng mới")
print(q2.messages) # ["Đơn hàng mới"] ← Bug!python
# ✅ ĐÚNG — khởi tạo trong __init__
class NotificationQueue:
def __init__(self) -> None:
self.messages: list[str] = []
def add(self, msg: str) -> None:
self.messages.append(msg)Tại sao sai: Class attribute messages = [] tạo một list duy nhất trong NotificationQueue.__dict__. Mọi instance trỏ đến cùng list đó. Trong production, bug này khiến notification của user A xuất hiện ở user B — nghiêm trọng về bảo mật dữ liệu.
❌ Sai lầm 2: Quên self trong tham số method
python
# ❌ SAI — thiếu self
class Calculator:
def __init__(self) -> None:
self.result = 0
def add(value: int) -> None: # Thiếu self!
self.result += value # NameError: name 'self' is not defined
calc = Calculator()
calc.add(5) # TypeError: add() takes 1 positional argument but 2 were givenpython
# ✅ ĐÚNG
class Calculator:
def __init__(self) -> None:
self.result = 0
def add(self, value: int) -> None:
self.result += valueTại sao sai: Khi gọi calc.add(5), Python truyền calc làm argument đầu tiên. Nếu method chỉ có value, thì calc bị gán vào value và 5 trở thành argument thừa. Message lỗi "takes 1 but 2 were given" là dấu hiệu nhận biết kinh điển.
❌ Sai lầm 3: Nhầm mutation và reassignment trên class attribute
python
class Config:
settings = {"debug": False}
c1 = Config()
c2 = Config()
# Mutation — ảnh hưởng TẤT CẢ instances
c1.settings["debug"] = True
print(c2.settings["debug"]) # True ← Bug!
# Reassignment — chỉ ảnh hưởng c1
c1.settings = {"debug": False} # Tạo entry mới trong c1.__dict__
print(c2.settings["debug"]) # True ← c2 vẫn dùng class attribute cũTại sao sai: c1.settings["debug"] = True là mutation — sửa dict tại chỗ, class attribute bị thay đổi. c1.settings = {...} là reassignment — tạo instance attribute mới shadow class attribute. Hai thao tác trông giống nhau nhưng hành vi khác nhau hoàn toàn — nếu không phân biệt, bug sẽ xuất hiện rất khó đoán.
❌ Sai lầm 4: Mutable default trong __init__ parameter
python
# ❌ SAI — list default chia sẻ giữa mọi lần gọi
def __init__(self, tags: list[str] = []) -> None:
self.tags = tagspython
# ✅ ĐÚNG — dùng None sentinel
def __init__(self, tags: list[str] | None = None) -> None:
self.tags = tags if tags is not None else []Tại sao sai: Default argument trong Python được evaluate một lần duy nhất khi function được define, không phải mỗi lần gọi. Mọi instance không truyền tags sẽ dùng chung cùng một list object. Đây là gotcha nổi tiếng nhất của Python.
Under the Hood
Cấu trúc object trong bộ nhớ
Mỗi Python object ở tầng C (CPython) là một struct PyObject chứa ít nhất hai trường:
| Trường | Ý nghĩa |
|---|---|
ob_refcnt | Đếm số tham chiếu — phục vụ garbage collection |
ob_type | Con trỏ đến type object (class) của instance này |
Khi bạn viết user = User("An", "an@x.vn"), CPython thực hiện:
type.__call__(User, "An", "an@x.vn")
├── User.__new__(User) → Cấp phát PyObject trên heap
└── User.__init__(instance, "An", "an@x.vn") → Gán dữ liệu__dict__ vs __slots__
Mặc định, mỗi instance có một __dict__ — dictionary chứa toàn bộ instance attributes. Linh hoạt nhưng tốn bộ nhớ:
python
class WithDict:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
obj = WithDict(1, 2)
print(obj.__dict__) # {'x': 1, 'y': 2}
obj.z = 3 # ✅ Thêm attribute mới — dynamic__slots__ thay __dict__ bằng fixed-size struct, tiết kiệm bộ nhớ nhưng mất tính dynamic:
python
class WithSlots:
__slots__ = ("x", "y")
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
obj = WithSlots(1, 2)
obj.z = 3 # ❌ AttributeError — không có __dict__So sánh:
| Tiêu chí | __dict__ | __slots__ |
|---|---|---|
| Bộ nhớ / instance | ~100-200 bytes overhead | ~40-60 bytes overhead |
| Thêm attribute mới | ✅ Có | ❌ Không |
| Tốc độ truy cập | Hash lookup | Direct offset (nhanh hơn ~20%) |
| Khi nào dùng | Mặc định, phần lớn cases | Hàng triệu instance, attribute cố định |
Attribute lookup chain đầy đủ
Khi Python resolve obj.attr, thứ tự ưu tiên thực tế phức tạp hơn nhiều so với mô hình đơn giản:
obj.attr
1. Data descriptor trong type(obj).__mro__ (có __get__ VÀ __set__)
2. obj.__dict__ (instance attributes)
3. Non-data descriptor / class attribute (chỉ có __get__)
4. __getattr__(self, name) (fallback hook)
→ AttributeError nếu không tìm thấyData descriptor (như property, classmethod) có ưu tiên cao hơn instance attribute. Đó là lý do @property hoạt động — nó "chặn" lookup trước khi Python tìm trong obj.__dict__.
python
class Example:
@property
def value(self) -> int:
return 42
obj = Example()
obj.__dict__["value"] = 99 # Ghi trực tiếp vào __dict__
print(obj.value) # 42 — property (data descriptor) thắng!Checklist ghi nhớ
✅ Checklist triển khai
Thiết kế class
- [ ] Tên class dùng PascalCase, tên method/attribute dùng snake_case
- [ ] Mỗi class có docstring mô tả purpose và public attributes
- [ ] Class chủ yếu chứa dữ liệu → cân nhắc
@dataclass - [ ] Class có hành vi phức tạp, lifecycle management → class truyền thống
__init__ và khởi tạo
- [ ] Validate dữ liệu đầu vào ngay trong
__init__— fail fast - [ ] Không dùng mutable default (
[],{}) làm parameter default - [ ] Dùng
Nonesentinel + tạo mới trong body cho mutable defaults - [ ] Type hint đầy đủ cho parameters và return type (
-> None)
Attributes
- [ ] Instance attributes khởi tạo trong
__init__, không ở class body - [ ] Class attributes chỉ dùng cho hằng số hoặc immutable values
- [ ] Nắm rõ khác biệt mutation vs reassignment trên class attribute
- [ ] Dùng
@propertycho computed values thay vì attribute thường
Dataclass
- [ ]
field(default_factory=list)cho mutable defaults - [ ]
__post_init__cho validation và computed fields - [ ]
frozen=Truekhi cần immutability (value objects, dict keys) - [ ]
slots=True(Python 3.10+) khi tạo hàng triệu instance
Bài tập luyện tập
Bài 1 — BankAccount (Foundation)
Tạo class BankAccount với yêu cầu:
- Attributes:
account_number(str),owner_name(str),balance(Decimal, mặc định 0) - Method
deposit(amount): cộng tiền, raiseValueErrornếuamount <= 0 - Method
withdraw(amount): trừ tiền, raiseValueErrornếuamount <= 0hoặc không đủ số dư - Property
is_overdrawn: trả vềTruenếubalance < 0 __repr__hiển thị thông tin account
python
# Expected behavior:
acc = BankAccount("VN-001", "Nguyễn Văn A", Decimal("10_000_000"))
acc.deposit(Decimal("5_000_000"))
print(acc.balance) # 15000000
acc.withdraw(Decimal("3_000_000"))
print(acc.balance) # 12000000Lời giải Bài 1
python
from decimal import Decimal
class BankAccount:
"""Tài khoản ngân hàng với validation đầy đủ."""
def __init__(
self,
account_number: str,
owner_name: str,
balance: Decimal = Decimal("0"),
) -> None:
if not account_number.strip():
raise ValueError("Số tài khoản không được rỗng")
self.account_number = account_number.strip()
self.owner_name = owner_name.strip()
self.balance = balance
def deposit(self, amount: Decimal) -> None:
if amount <= 0:
raise ValueError(f"Số tiền nạp phải > 0, nhận {amount}")
self.balance += amount
def withdraw(self, amount: Decimal) -> None:
if amount <= 0:
raise ValueError(f"Số tiền rút phải > 0, nhận {amount}")
if amount > self.balance:
raise ValueError(
f"Số dư không đủ: hiện {self.balance}, yêu cầu {amount}"
)
self.balance -= amount
@property
def is_overdrawn(self) -> bool:
return self.balance < 0
def __repr__(self) -> str:
return (
f"BankAccount(account_number={self.account_number!r}, "
f"owner_name={self.owner_name!r}, balance={self.balance})"
)Bài 2 — Chuyển đổi sang @dataclass (Intermediate)
Cho class Employee truyền thống (có emp_id, name, email, monthly_salary, skills), chuyển sang @dataclass với:
- Validation: email chứa
@, salary > 0 - Computed field
annual_salary = monthly_salary * 12 field(default_factory=list)choskills
Lời giải Bài 2
python
from dataclasses import dataclass, field
@dataclass
class Employee:
"""Nhân viên — phiên bản dataclass."""
emp_id: str
name: str
email: str
monthly_salary: float
skills: list[str] = field(default_factory=list)
# Computed field — không nhận từ __init__
annual_salary: float = field(init=False, repr=False)
def __post_init__(self) -> None:
if "@" not in self.email:
raise ValueError(f"Email không hợp lệ: {self.email}")
if self.monthly_salary <= 0:
raise ValueError(
f"Lương phải > 0, nhận {self.monthly_salary}"
)
self.annual_salary = self.monthly_salary * 12
# Test
emp = Employee("E-001", "Trần B", "b@corp.vn", 20_000_000.0)
print(emp) # Employee(emp_id='E-001', name='Trần B', ...)
print(emp.annual_salary) # 240000000.0
print(emp.skills) # [] — list riêng, không chia sẻ🧠 Quiz
Câu hỏi kiểm tra: Cho đoạn code sau, team_b.members in ra kết quả gì?
python
class Team:
members = []
def add(self, name: str) -> None:
self.members.append(name)
team_a = Team()
team_b = Team()
team_a.add("An")
team_a.add("Bình")
print(team_b.members)- [ ] A.
[]— team_b có list riêng, không bị ảnh hưởng - [ ] B.
["An"]— chỉ lấy phần tử đầu - [x] C.
["An", "Bình"]— class attribute chia sẻ giữa mọi instance - [ ] D.
AttributeError— team_b không có attributemembers
Giải thích: members = [] là class attribute — một list duy nhất tồn tại trong Team.__dict__. Khi team_a.add("An") gọi self.members.append(...), Python tìm members trong team_a.__dict__ (không có) → fallback lên Team.__dict__ → tìm thấy list → append vào đó. team_b cũng lookup cùng list → thấy ["An", "Bình"]. Fix: khởi tạo self.members = [] trong __init__.