Skip to content

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   Instance

Tươ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____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ướcMethodVai 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 = 42

Khi 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)  # 1

Khi 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 local

So 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 class

Chuỗ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 AttributeError

Bẫ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()__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)  # VND

frozen=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 đổi

slots=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 instance

Thự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)            # 12

Cá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ơ" — đã strip

Trade-off giữa hai cách:

Tiêu chíClass truyền thống@dataclass
BoilerplateNhiềuÍt
__repr__/__eq__Viết tayTự động sinh
Validation phức tạpLinh hoạt trong __init__Dồn vào __post_init__
Computed fields@property — giống nhaufield(init=False) + __post_init__
ImmutabilityTự quản lýfrozen=True
PerformanceTương đươngslots=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 given
python
# ✅ ĐÚNG
class Calculator:
    def __init__(self) -> None:
        self.result = 0

    def add(self, value: int) -> None:
        self.result += value

Tạ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 value5 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"] = Truemutation — sửa dict tại chỗ, class attribute bị thay đổi. c1.settings = {...}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 = tags
python
# ✅ ĐÚ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_typeCon 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ậpHash lookupDirect offset (nhanh hơn ~20%)
Khi nào dùngMặc định, phần lớn casesHà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ấy

Data 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 None sentinel + 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 @property cho computed values thay vì attribute thường

Dataclass

  • [ ] field(default_factory=list) cho mutable defaults
  • [ ] __post_init__ cho validation và computed fields
  • [ ] frozen=True khi 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, raise ValueError nếu amount <= 0
  • Method withdraw(amount): trừ tiền, raise ValueError nếu amount <= 0 hoặc không đủ số dư
  • Property is_overdrawn: trả về True nếu balance < 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)  # 12000000
Lờ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) cho skills
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ó attribute members

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__.


Liên kết học tiếp