Skip to content

Kế thừa và MRO trong Python

"Favor composition over inheritance" — Gang of Four viết câu này năm 1994 và ba mươi năm sau, nó vẫn đúng. Nhưng thực tế thì sao? Mỗi APIView trong Django REST Framework, mỗi model trong SQLAlchemy, mỗi BaseModel trong Pydantic — tất cả đều dùng kế thừa. Vấn đề không phải có nên dùng hay không, mà là dùng như thế nào cho đúng.

Python cho phép đa kế thừa (multiple inheritance) — một tính năng mà Java từ chối, C# hạn chế, nhưng Python ôm trọn. Cái giá phải trả? Diamond problem — khi hai class cha cùng kế thừa từ một class ông nội, Python phải quyết định gọi method nào trước. Giải pháp là MRO (Method Resolution Order) dựa trên thuật toán C3 linearization, đảm bảo mỗi class chỉ xuất hiện đúng một lần trong chuỗi giải quyết.

Hình dung tình huống: bạn kế thừa APIView trong Django REST, override method get(), và gọi super().get(request). Bạn có biết super() không đơn giản là "gọi class cha" — nó đi theo MRO, và trong đa kế thừa, class tiếp theo có thể không phải class cha trực tiếp? Hiểu sai điều này là nguồn gốc của hàng loạt bug khó debug trong production.


Bức tranh tư duy

Kế thừa giống như gia phả (family tree). Class cha truyền lại "gene" — tức attributes và methods — cho class con. Class con có thể giữ nguyên, override, hoặc mở rộng những gì nhận được.

Kế thừa đơn (single inheritance) thì đơn giản: một dòng thẳng từ ông → cha → con. Nhưng đa kế thừa (multiple inheritance) phức tạp hơn nhiều — như một đứa trẻ có hai bố mẹ. Và diamond problem xảy ra khi hai bố mẹ cùng có chung một ông nội:

Trong gia phả thực, tính cách con người là hỗn hợp không rõ ràng. Nhưng trong code, Python cần một thứ tự tuyến tính, duy nhất để quyết định method nào được gọi. Đó chính là MRO — biến cây kế thừa thành một danh sách phẳng: D → B → C → A → object.

⚠️ ANALOGY CÓ GIỚI HẠN

Gia phả là cây (tree), nhưng MRO là danh sách tuyến tính (linear list). Trong gia phả, con không thể thay đổi tính cách bố mẹ. Trong code, class con override method cha thoải mái. Đừng kéo analogy quá xa — hãy dùng nó để khởi động trực giác, rồi chuyển sang đọc code.


Cốt lõi kỹ thuật

Kế thừa đơn (Single Inheritance)

Cú pháp cơ bản: class con đặt class cha trong ngoặc tròn. Class con kế thừa toàn bộ attributes và methods của cha, có thể override hoặc extend.

python
from typing import Any


class BaseRepository:
    """Repository cơ sở — cung cấp CRUD interface."""

    def __init__(self, model_name: str) -> None:
        self.model_name = model_name
        self._store: dict[int, dict[str, Any]] = {}
        self._next_id: int = 1

    def create(self, data: dict[str, Any]) -> int:
        entity_id = self._next_id
        self._next_id += 1
        self._store[entity_id] = {**data, "id": entity_id}
        return entity_id

    def get_by_id(self, entity_id: int) -> dict[str, Any] | None:
        return self._store.get(entity_id)

    def delete(self, entity_id: int) -> bool:
        return self._store.pop(entity_id, None) is not None


class UserRepository(BaseRepository):
    """Repository cho User — kế thừa và mở rộng BaseRepository."""

    def __init__(self) -> None:
        super().__init__("User")  # Gọi __init__ cha, truyền model_name

    def create(self, data: dict[str, Any]) -> int:
        # Validate trước khi delegate cho cha
        if "email" not in data:
            raise ValueError("User phải có email")
        return super().create(data)  # Gọi create() của BaseRepository

    def find_by_email(self, email: str) -> dict[str, Any] | None:
        """Method mới — chỉ có ở UserRepository."""
        for entity in self._store.values():
            if entity.get("email") == email:
                return entity
        return None

Ba hành vi quan trọng:

  1. Kế thừa nguyên vẹn: get_by_id()delete() hoạt động mà UserRepository không cần viết lại
  2. Override: create() được viết lại với validation, rồi delegate về cha qua super()
  3. Extend: find_by_email() là method hoàn toàn mới, chỉ tồn tại ở class con

super() — gọi phương thức theo MRO

super() không đơn giản là "gọi class cha". Nó trả về một proxy object đi theo MRO để tìm method tiếp theo trong chuỗi. Trong kế thừa đơn, kết quả giống nhau. Nhưng trong đa kế thừa, sự khác biệt là sống còn.

python
class A:
    def greet(self) -> str:
        return "A"

class B(A):
    def greet(self) -> str:
        return f"B -> {super().greet()}"

class C(A):
    def greet(self) -> str:
        return f"C -> {super().greet()}"

class D(B, C):
    def greet(self) -> str:
        return f"D -> {super().greet()}"


# MRO: D -> B -> C -> A -> object
print(D().greet())  # D -> B -> C -> A
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

Quan sát: super() trong B.greet() không gọi A.greet() — nó gọi C.greet(), class tiếp theo trong MRO của D. Đây là lý do super() được gọi là cooperative — mỗi class phải "hợp tác" bằng cách gọi super() để chuỗi không bị đứt.

So sánh hai cách gọi:

Cách gọiHành viKhi nào dùng
super().__init__()Đi theo MRO, cooperativeMặc định — luôn dùng cách này
ParentClass.__init__(self)Gọi trực tiếp, bỏ qua MROChỉ khi cần skip MRO (rất hiếm)

🚨 PRODUCTION PITFALL

Nếu trong chuỗi đa kế thừa, một class dùng ParentClass.__init__(self) thay vì super().__init__(), nó sẽ phá vỡ chuỗi cooperative. Các class khác trong MRO có thể không được khởi tạo → bug ẩn, chỉ phát hiện khi runtime.

Đa kế thừa và Diamond Problem

Diamond problem xảy ra khi class D kế thừa từ B và C, cả hai đều kế thừa từ A. Câu hỏi: A.__init__() được gọi bao nhiêu lần?

python
class A:
    def __init__(self) -> None:
        print("A.__init__")
        super().__init__()

class B(A):
    def __init__(self) -> None:
        print("B.__init__")
        super().__init__()

class C(A):
    def __init__(self) -> None:
        print("C.__init__")
        super().__init__()

class D(B, C):
    def __init__(self) -> None:
        print("D.__init__")
        super().__init__()


d = D()
# Output:
# D.__init__
# B.__init__
# C.__init__
# A.__init__

A.__init__ chỉ được gọi một lần duy nhất — nhờ MRO. Thứ tự: D → B → C → A → object. Mỗi super() chuyển sang class tiếp theo trong MRO, không phải class cha trực tiếp.

Nếu thay super().__init__() bằng gọi trực tiếp:

python
class B(A):
    def __init__(self) -> None:
        print("B.__init__")
        A.__init__(self)  # Gọi trực tiếp — KHÔNG dùng super()

class C(A):
    def __init__(self) -> None:
        print("C.__init__")
        A.__init__(self)  # Gọi trực tiếp — KHÔNG dùng super()

# Kết quả: A.__init__ được gọi HAI LẦN
# → State bị reset, resource bị allocate đôi

C3 Linearization — thuật toán MRO

Python sử dụng thuật toán C3 linearization (giới thiệu từ Python 2.3) để tính MRO. Thuật toán đảm bảo ba tính chất:

  1. Monotonicity: Nếu C1 đứng trước C2 trong MRO của cha, thì C1 cũng đứng trước C2 trong MRO của con
  2. Local precedence order: Thứ tự khai báo trong class definition được tôn trọng
  3. No duplication: Mỗi class xuất hiện đúng một lần

Công thức đệ quy:

L[C(B1, B2, ...)] = C + merge(L[B1], L[B2], ..., [B1, B2, ...])

Trong đó merge() hoạt động như sau:

  • Lấy phần tử đầu tiên (head) của danh sách đầu tiên
  • Nếu head không xuất hiện ở tail (vị trí 2+ trở đi) của bất kỳ danh sách nào khác → chọn nó, loại khỏi tất cả danh sách
  • Nếu head bị chặn → thử head của danh sách tiếp theo
  • Lặp lại cho đến khi tất cả danh sách rỗng (thành công) hoặc không tìm được head hợp lệ (thất bại)

Ví dụ tính tay:

python
class O: pass    # object
class A(O): pass
class B(O): pass
class C(A, B): pass
class D(B, A): pass
# class E(C, D): pass  # sẽ gây lỗi!

Tính MRO cho C:

L[C] = C + merge(L[A], L[B], [A, B])
     = C + merge([A, O], [B, O], [A, B])
     # Head = A, A không ở tail của list nào → chọn A
     = C + A + merge([O], [B, O], [B])
     # Head = O, O ở tail của [B, O] → bỏ qua, thử B
     # Head = B, B không ở tail của list nào → chọn B
     = C + A + B + merge([O], [O])
     = C + A + B + O

Tính MRO cho D:

L[D] = D + merge(L[B], L[A], [B, A])
     = D + B + A + O

Thử tạo E(C, D):

L[E] = E + merge(L[C], L[D], [C, D])
     = E + merge([C, A, B, O], [D, B, A, O], [C, D])
     = E + C + merge([A, B, O], [D, B, A, O], [D])
     = E + C + D + merge([A, B, O], [B, A, O])
     # Head = A, nhưng A ở tail [B, A, O] → blocked
     # Head = B, nhưng B ở tail [A, B, O] → blocked
     # → THẤT BẠI: Inconsistent MRO
python
# Python raise TypeError khi MRO không giải được
try:
    class E(C, D): pass
except TypeError as e:
    print(e)
    # Cannot create a consistent method resolution order (MRO)
    # for bases A, B

Mixin pattern

Mixin là class nhỏ, đơn trách nhiệm, thêm hành vi (behavior) cho class chính mà không tạo quan hệ "is-a". Quy tắc vàng:

  1. Mixin không nên có __init__ riêng — hoặc nếu có, phải dùng **kwargs cooperative pattern
  2. Mixin đặt trước class chính trong danh sách kế thừa: class X(MixinA, MixinB, Base)
  3. Mixin không phụ thuộc vào state của class khác — nó thêm behavior, không đọc/ghi attribute bất kỳ
python
import json
import logging
from datetime import datetime, timezone
from typing import Any


class LoggableMixin:
    """Thêm logging capability cho bất kỳ class nào."""

    @property
    def logger(self) -> logging.Logger:
        return logging.getLogger(self.__class__.__name__)

    def log_action(self, action: str, **details: Any) -> None:
        self.logger.info(
            "%s.%s | %s",
            self.__class__.__name__,
            action,
            json.dumps(details, default=str),
        )


class TimestampMixin:
    """Tự động track created_at và updated_at."""

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(**kwargs)  # Forward kwargs cho class tiếp theo trong MRO
        self.created_at: datetime = datetime.now(timezone.utc)
        self.updated_at: datetime = self.created_at

    def touch(self) -> None:
        """Cập nhật updated_at."""
        self.updated_at = datetime.now(timezone.utc)


class SerializableMixin:
    """Cho phép serialize object thành dict/JSON."""

    def to_dict(self) -> dict[str, Any]:
        return {
            key: value
            for key, value in self.__dict__.items()
            if not key.startswith("_")
        }

    def to_json(self) -> str:
        return json.dumps(self.to_dict(), default=str)

Cách dùng mixin trong thực tế — Django-style:

python
class BaseModel:
    """Model cơ sở."""

    def __init__(self, **kwargs: Any) -> None:
        for key, value in kwargs.items():
            setattr(self, key, value)


class User(TimestampMixin, SerializableMixin, LoggableMixin, BaseModel):
    """User model với logging, timestamp, serialization."""

    def __init__(self, name: str, email: str, **kwargs: Any) -> None:
        super().__init__(name=name, email=email, **kwargs)
        self.name = name
        self.email = email
        self.log_action("created", name=name, email=email)


# MRO: User -> TimestampMixin -> SerializableMixin -> LoggableMixin -> BaseModel -> object
user = User(name="Minh", email="minh@penalgo.dev")
print(user.to_json())
# {"created_at": "2025-...", "updated_at": "2025-...", "name": "Minh", "email": "minh@penalgo.dev"}

Lưu ý thứ tự MRO: TimestampMixin đứng trước BaseModel, nên TimestampMixin.__init__ gọi super().__init__(**kwargs) sẽ chuyển tiếp xuống BaseModel.__init__ — chuỗi cooperative hoạt động đúng nhờ **kwargs.


Thực chiến

Tình huống: Hệ thống Repository pattern với Mixins

Bạn xây dựng một backend service cần các repository cho nhiều entity (User, Product, Order). Mỗi repository cần CRUD cơ bản, nhưng tùy entity lại cần thêm: caching, logging, pagination. Thay vì copy-paste hoặc tạo class cha khổng lồ, ta dùng mixin composition.

python
from __future__ import annotations

import json
import logging
import time
from typing import Any, Generic, TypeVar

T = TypeVar("T", bound=dict[str, Any])


class BaseRepository(Generic[T]):
    """Repository cơ sở — CRUD thuần túy."""

    def __init__(self, model_name: str, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self.model_name = model_name
        self._store: dict[int, T] = {}
        self._next_id: int = 1
        self._logger = logging.getLogger(f"repo.{model_name}")

    def create(self, data: T) -> int:
        entity_id = self._next_id
        self._next_id += 1
        record = {**data, "id": entity_id}
        self._store[entity_id] = record  # type: ignore[assignment]
        return entity_id

    def get_by_id(self, entity_id: int) -> T | None:
        return self._store.get(entity_id)

    def list_all(self) -> list[T]:
        return list(self._store.values())

    def delete(self, entity_id: int) -> bool:
        return self._store.pop(entity_id, None) is not None


class CacheableMixin:
    """Thêm in-memory cache cho get_by_id."""

    def __init__(self, cache_ttl: int = 60, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self._cache: dict[int, tuple[float, Any]] = {}
        self._cache_ttl = cache_ttl

    def get_by_id(self, entity_id: int) -> Any | None:
        # Kiểm tra cache trước
        if entity_id in self._cache:
            cached_time, cached_value = self._cache[entity_id]
            if time.time() - cached_time < self._cache_ttl:
                return cached_value
            del self._cache[entity_id]

        # Cache miss → gọi class tiếp theo trong MRO
        result = super().get_by_id(entity_id)  # type: ignore[misc]
        if result is not None:
            self._cache[entity_id] = (time.time(), result)
        return result

    def invalidate(self, entity_id: int) -> None:
        self._cache.pop(entity_id, None)


class LoggableMixin:
    """Log mọi thao tác CRUD."""

    _op_logger = logging.getLogger("repo.operations")

    def create(self, data: Any) -> int:
        result = super().create(data)  # type: ignore[misc]
        self._op_logger.info("CREATE %s #%d", self.__class__.__name__, result)
        return result

    def delete(self, entity_id: int) -> bool:
        result = super().delete(entity_id)  # type: ignore[misc]
        level = logging.INFO if result else logging.WARNING
        self._op_logger.log(
            level, "DELETE %s #%d -> %s", self.__class__.__name__, entity_id, result
        )
        return result


class PaginationMixin:
    """Thêm pagination cho list_all."""

    def list_all(  # type: ignore[override]
        self, *, page: int = 1, page_size: int = 20
    ) -> dict[str, Any]:
        all_items: list[Any] = super().list_all()  # type: ignore[misc]
        total = len(all_items)
        start = (page - 1) * page_size
        end = start + page_size
        return {
            "items": all_items[start:end],
            "total": total,
            "page": page,
            "page_size": page_size,
            "total_pages": (total + page_size - 1) // page_size,
        }


# ============================================
# Kết hợp: UserRepository với đầy đủ mixins
# ============================================

class UserRepository(
    CacheableMixin,
    LoggableMixin,
    PaginationMixin,
    BaseRepository[dict[str, Any]],
):
    """Repository cho User — có cache, logging, pagination."""

    def __init__(self) -> None:
        super().__init__(model_name="User", cache_ttl=120)

    def create(self, data: dict[str, Any]) -> int:
        if "email" not in data:
            raise ValueError("User phải có email")
        entity_id = super().create(data)
        self.invalidate(entity_id)  # Invalidate cache khi tạo mới
        return entity_id


# Kiểm tra MRO
print("MRO của UserRepository:")
for i, cls in enumerate(UserRepository.__mro__):
    print(f"  {i}. {cls.__name__}")
# 0. UserRepository
# 1. CacheableMixin
# 2. LoggableMixin
# 3. PaginationMixin
# 4. BaseRepository
# 5. Generic
# 6. object

Phân tích luồng get_by_id(42):

  1. UserRepository không override get_by_id → MRO tìm tiếp
  2. CacheableMixin.get_by_id() — kiểm tra cache
  3. Cache miss → super().get_by_id() → MRO tiếp: LoggableMixin không có get_by_id
  4. Tiếp → PaginationMixin không có get_by_id
  5. Tiếp → BaseRepository.get_by_id() — trả về data từ _store
  6. Kết quả trả ngược về CacheableMixin → lưu cache → trả cho caller

Luồng create(data):

  1. UserRepository.create() — validate email
  2. super().create()CacheableMixin không override create → skip
  3. LoggableMixin.create() — log, rồi gọi super().create()
  4. PaginationMixin không override create → skip
  5. BaseRepository.create() — lưu vào _store

💡 THIẾT KẾ MIXIN

Thứ tự mixin quan trọng. Đặt mixin cần chạy trước (cache check) ở đầu danh sách kế thừa. Mixin "decorator-style" (logging) đặt giữa. Class cơ sở (BaseRepository) luôn ở cuối.


Sai lầm điển hình

Sai lầm 1: Quên gọi super().__init__()

python
# ❌ SAI — attribute của BaseRepository không được khởi tạo
class ProductRepository(BaseRepository):
    def __init__(self, category: str) -> None:
        self.category = category
        # Quên super().__init__() → self._store không tồn tại!

repo = ProductRepository("electronics")
# repo.create({"name": "Laptop"})  # AttributeError: '_store'
python
# ✅ ĐÚNG — luôn gọi super().__init__()
class ProductRepository(BaseRepository):
    def __init__(self, category: str) -> None:
        super().__init__(model_name="Product")
        self.category = category

Production impact: AttributeError chỉ xảy ra khi method cha được gọi lần đầu — có thể là hàng giờ sau deploy. Nếu không có test coverage đầy đủ, bug này lọt vào production dễ dàng.

Sai lầm 2: Không forward **kwargs trong cooperative inheritance

python
# ❌ SAI — chain bị đứt, BaseRepository không nhận model_name
class AuditMixin:
    def __init__(self, audit_enabled: bool = True) -> None:
        super().__init__()  # kwargs bị nuốt!
        self.audit_enabled = audit_enabled

class AuditedRepo(AuditMixin, BaseRepository):
    def __init__(self) -> None:
        super().__init__(model_name="Audit", audit_enabled=True)
        # TypeError: AuditMixin.__init__() got unexpected keyword argument 'model_name'
python
# ✅ ĐÚNG — forward **kwargs qua toàn bộ chain
class AuditMixin:
    def __init__(self, audit_enabled: bool = True, **kwargs: Any) -> None:
        super().__init__(**kwargs)  # Forward mọi kwargs chưa dùng
        self.audit_enabled = audit_enabled

class AuditedRepo(AuditMixin, BaseRepository):
    def __init__(self) -> None:
        super().__init__(model_name="Audit", audit_enabled=True)
        # AuditMixin nhận audit_enabled, forward model_name → BaseRepository ✓

Production impact: Lỗi này thường chỉ phát hiện khi thêm mixin mới vào class đã hoạt động — thêm một class vào MRO mà quên forward kwargs sẽ break toàn bộ chain.

Sai lầm 3: Cây kế thừa quá sâu (> 3 tầng)

python
# ❌ SAI — 5 tầng kế thừa, không ai hiểu method nào từ đâu
class BaseEntity: ...
class TimestampedEntity(BaseEntity): ...
class SoftDeletableEntity(TimestampedEntity): ...
class AuditableEntity(SoftDeletableEntity): ...
class VersionedEntity(AuditableEntity): ...
class User(VersionedEntity): ...  # MRO dài 7 class!
python
# ✅ ĐÚNG — dùng composition hoặc flat mixin
class User(
    TimestampMixin,
    SoftDeleteMixin,
    AuditMixin,
    VersionMixin,
    BaseEntity,
):
    """Flat hierarchy — MRO rõ ràng, dễ debug."""
    ...

Production impact: Kế thừa sâu tạo fragile base class problem — thay đổi ở tầng 2 có thể break tầng 5. Debug phải trace qua nhiều file. Onboard developer mới mất hàng tuần chỉ để hiểu class hierarchy.

Sai lầm 4: Mixin giữ state phức tạp

python
# ❌ SAI — Mixin khởi tạo database connection = side effect nặng
class DatabaseMixin:
    def __init__(self) -> None:
        import sqlite3
        self.conn = sqlite3.connect("app.db")  # Side effect trong mixin!
        self.cursor = self.conn.cursor()
python
# ✅ ĐÚNG — Mixin cung cấp behavior, inject dependency từ bên ngoài
class DatabaseMixin:
    """Mixin chỉ cung cấp helper methods, không tạo connection."""

    _db_connection = None  # Class-level, set từ bên ngoài

    @classmethod
    def configure_db(cls, connection: Any) -> None:
        cls._db_connection = connection

    def execute_query(self, query: str, params: tuple = ()) -> Any:
        if self._db_connection is None:
            raise RuntimeError("Database chưa được configure. Gọi configure_db() trước.")
        cursor = self._db_connection.cursor()
        return cursor.execute(query, params)

Production impact: Mixin có side effect (mở file, tạo connection, gọi API) khiến unit test phải mock phức tạp, và mixin không thể reuse trong context khác (ví dụ: test dùng in-memory DB, production dùng PostgreSQL).


Under the Hood

Cách CPython lưu MRO

Khi Python thực thi class D(B, C), CPython gọi type.__new__() để tạo type object. Trong quá trình này:

  1. tp_bases: tuple chứa base classes trực tiếp → (B, C)
  2. tp_mro: tuple chứa MRO đầy đủ, tính bằng C3 → (D, B, C, A, object)

MRO được tính một lần duy nhất tại thời điểm tạo class và lưu trong type.__mro__. Nó không thay đổi sau đó (trừ khi dùng metaclass tricks).

python
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

# Truy cập MRO
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

# __bases__ chỉ chứa cha trực tiếp
print(D.__bases__)
# (<class 'B'>, <class 'C'>)

C3 merge algorithm — Python implementation

python
def c3_linearize(cls: type) -> list[type]:
    """Mô phỏng thuật toán C3 linearization."""
    if cls is object:
        return [object]

    bases = cls.__bases__
    # Danh sách linearization của từng base + danh sách bases
    linearizations = [c3_linearize(base) for base in bases]
    linearizations.append(list(bases))

    result: list[type] = [cls]

    while any(linearizations):
        # Tìm head không bị chặn
        for lin in linearizations:
            if not lin:
                continue
            head = lin[0]
            # head bị chặn nếu nó xuất hiện ở tail của bất kỳ list nào
            blocked = any(head in other[1:] for other in linearizations if other)
            if not blocked:
                result.append(head)
                # Loại head khỏi tất cả danh sách
                for other in linearizations:
                    if other and other[0] is head:
                        other.pop(0)
                break
        else:
            raise TypeError(f"Cannot create consistent MRO for {cls}")

    return result


# Kiểm tra
print(c3_linearize(D))
# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]

super() internals

super() trong Python 3 (không argument) thực chất là super(__class__, self) — compiler tự inject __class__ cell variable. Kết quả là một super proxy object với hai thuộc tính:

  • __thisclass__: class mà super() được gọi
  • __self_class__: type thực tế của self

Khi truy cập attribute qua super proxy, nó bắt đầu tìm từ vị trí sau __thisclass__ trong MRO của __self_class__:

python
class B(A):
    def method(self):
        proxy = super()  # super(B, self)
        # proxy tìm method từ vị trí SAU B trong MRO của type(self)
        # Nếu type(self) là D, MRO = [D, B, C, A, object]
        # → proxy bắt đầu từ C (vị trí sau B)

Performance: method lookup

Method lookup đi qua MRO tuần tự — O(n) với n = chiều dài MRO. CPython có method cache (per-type) để tăng tốc, nhưng cache bị invalidate khi:

  • Class được modify (thêm/xóa attribute)
  • Base class thay đổi
  • __dict__ thay đổi
Thao tácComplexityGhi chú
Method lookup (uncached)O(n), n = len(MRO)Duyệt tuần tự __mro__
Method lookup (cached)O(1) amortizedMethod cache per-type
isinstance(obj, cls)O(n)Kiểm tra cls có trong MRO không
type(obj) is clsO(1)So sánh trực tiếp, không check MRO
MRO computationO(n squared) worst caseChỉ tính 1 lần khi tạo class

isinstance() vs type()

python
class Animal: pass
class Dog(Animal): pass

dog = Dog()

# isinstance kiểm tra toàn bộ MRO
isinstance(dog, Animal)  # True — Animal nằm trong Dog.__mro__
isinstance(dog, Dog)     # True

# type() chỉ kiểm tra class trực tiếp
type(dog) is Animal  # False — type(dog) là Dog, không phải Animal
type(dog) is Dog     # True

Quy tắc: Dùng isinstance() khi kiểm tra "is-a" relationship (polymorphism). Dùng type() is khi cần exact type match (factory pattern, serialization).


Checklist ghi nhớ

✅ Checklist triển khai

Thiết kế kế thừa

  • [ ] Class hierarchy không quá 3 tầng sâu — nếu sâu hơn, refactor sang mixin hoặc composition
  • [ ] Mỗi class con thực sự "is-a" class cha — nếu không, dùng composition
  • [ ] Interface/behavior kế thừa (ABC, Protocol) được ưu tiên hơn implementation inheritance
  • [ ] Fragile base class problem đã được cân nhắc — thay đổi ở cha không break con

super() và MRO

  • [ ] Mọi __init__ đều gọi super().__init__() — không bao giờ bỏ qua
  • [ ] **kwargs được forward trong mọi __init__ của mixin/cooperative class
  • [ ] Không gọi ParentClass.__init__(self) trực tiếp trừ khi có lý do rõ ràng
  • [ ] __mro__ đã được kiểm tra cho class phức tạp — dùng ClassName.__mro__ để verify

Multiple Inheritance & Mixins

  • [ ] Mixin đặt trước class chính trong danh sách kế thừa
  • [ ] Mixin không có side effect trong __init__ (không mở file, không tạo connection)
  • [ ] Mixin đặt tên với hậu tố MixinLoggableMixin, không phải Loggable
  • [ ] MRO không có inconsistency — Python raise TypeError nếu C3 thất bại

Composition vs Inheritance

  • [ ] "Has-a" dùng composition, "Is-a" dùng inheritance
  • [ ] Khi phân vân giữa hai cách, mặc định chọn composition
  • [ ] Strategy pattern được cân nhắc thay cho inheritance khi behavior thay đổi runtime

Bài tập luyện tập

Bài 1: Kế thừa cơ bản — Animal Hierarchy

Đề bài: Tạo hệ thống class cho động vật:

  • Animal (base): có name, sound, method speak() trả về f"{name} says {sound}"
  • Dog(Animal): sound mặc định "Woof", thêm breed attribute
  • Cat(Animal): sound mặc định "Meow", thêm indoor: bool attribute
  • Kitten(Cat): override speak() thêm " softly" vào cuối

Yêu cầu:

  • Mọi __init__ phải gọi super() đúng cách
  • Thêm __repr__ cho mỗi class
  • Kiểm tra isinstance() hoạt động đúng
python
# Test cases
dog = Dog("Rex", breed="Labrador")
assert dog.speak() == "Rex says Woof"
assert isinstance(dog, Animal)

kitten = Kitten("Miu", indoor=True)
assert kitten.speak() == "Miu says Meow softly"
assert isinstance(kitten, Cat)
assert isinstance(kitten, Animal)
💡 Gợi ý
  • Animal.__init__ nhận namesound, gọi super().__init__()
  • Dog.__init__ nhận namebreed, truyền sound="Woof" cho Animal
  • Kitten.speak() gọi super().speak() rồi thêm " softly"
✅ Lời giải
python
class Animal:
    def __init__(self, name: str, sound: str = "...") -> None:
        super().__init__()
        self.name = name
        self.sound = sound

    def speak(self) -> str:
        return f"{self.name} says {self.sound}"

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(name={self.name!r})"


class Dog(Animal):
    def __init__(self, name: str, *, breed: str = "Mixed") -> None:
        super().__init__(name, sound="Woof")
        self.breed = breed

    def __repr__(self) -> str:
        return f"Dog(name={self.name!r}, breed={self.breed!r})"


class Cat(Animal):
    def __init__(self, name: str, *, indoor: bool = False) -> None:
        super().__init__(name, sound="Meow")
        self.indoor = indoor

    def __repr__(self) -> str:
        return f"Cat(name={self.name!r}, indoor={self.indoor})"


class Kitten(Cat):
    def speak(self) -> str:
        return f"{super().speak()} softly"

    def __repr__(self) -> str:
        return f"Kitten(name={self.name!r}, indoor={self.indoor})"


# Verify
dog = Dog("Rex", breed="Labrador")
assert dog.speak() == "Rex says Woof"
assert isinstance(dog, Animal)

kitten = Kitten("Miu", indoor=True)
assert kitten.speak() == "Miu says Meow softly"
assert isinstance(kitten, Cat)
assert isinstance(kitten, Animal)
print("All tests passed")

Bài 2: Tính MRO bằng tay — Diamond Hierarchy

Đề bài: Cho hệ thống class sau:

python
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(C): pass
class F(D, E): pass
  1. Vẽ sơ đồ kế thừa
  2. Tính MRO của F bằng tay sử dụng thuật toán C3 linearization
  3. Verify kết quả bằng F.__mro__
💡 Gợi ý
  • Tính từ dưới lên: L[A], L[B], L[C], L[D], L[E], rồi L[F]
  • Nhớ công thức: L[C(B1, B2)] = C + merge(L[B1], L[B2], [B1, B2])
  • Head hợp lệ = không xuất hiện ở tail (vị trí [1:]) của bất kỳ danh sách nào
✅ Lời giải
L[A] = [A, O]
L[B] = [B, A, O]
L[C] = [C, A, O]

L[D] = D + merge(L[B], L[C], [B, C])
     = D + merge([B, A, O], [C, A, O], [B, C])
     = D + B + merge([A, O], [C, A, O], [C])
     # A ở tail [C, A, O] -> blocked. Thử C
     = D + B + C + merge([A, O], [A, O])
     = D + B + C + A + O
     -> L[D] = [D, B, C, A, O]

L[E] = E + merge(L[C], [C])
     = E + C + A + O
     -> L[E] = [E, C, A, O]

L[F] = F + merge(L[D], L[E], [D, E])
     = F + merge([D, B, C, A, O], [E, C, A, O], [D, E])
     = F + D + merge([B, C, A, O], [E, C, A, O], [E])
     = F + D + B + merge([C, A, O], [E, C, A, O], [E])
     # C ở tail [E, C, A, O] -> blocked. Thử E
     = F + D + B + E + merge([C, A, O], [C, A, O])
     = F + D + B + E + C + A + O
python
# Verify
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(C): pass
class F(D, E): pass

expected = (F, D, B, E, C, A, object)
assert F.__mro__ == expected, f"Got {F.__mro__}"
print("MRO verified")
print(" -> ".join(cls.__name__ for cls in F.__mro__))
# F -> D -> B -> E -> C -> A -> object

🧠 Quiz

Câu hỏi: Cho class X(A, B, C) trong đó A, B, C đều kế thừa từ object. MRO của X là gì?

  • [ ] A. X -> A -> B -> C -> object -> object -> object
  • [x] B. X -> A -> B -> C -> object
  • [ ] C. X -> C -> B -> A -> object
  • [ ] D. X -> object -> A -> B -> C

Giải thích: MRO đảm bảo mỗi class chỉ xuất hiện một lần. object là base chung, luôn ở cuối. Thứ tự A, B, C giữ nguyên local precedence order — tức thứ tự khai báo trong class X(A, B, C). Đáp án C sai vì đảo ngược thứ tự. Đáp án A sai vì object xuất hiện nhiều lần. Đáp án D sai vì object không thể đứng trước các class khác.


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

Glossary keywords: inheritance, kế thừa, MRO, method resolution order, C3 linearization, super(), diamond problem, mixin, multiple inheritance, cooperative inheritance, fragile base class

Search aliases: python kế thừa, MRO là gì, diamond problem python, super() hoạt động thế nào, mixin pattern python, đa kế thừa python