Skip to content

Metaclasses trong Python Expert

"Metaclasses are deeper magic than 99% of users should ever worry about." — Tim Peters

Nhưng bạn không phải 99% lập trình viên thông thường. Nếu bạn đang xây dựng framework, ORM, plugin system, hay validation engine — metaclass chính là công cụ mạnh mẽ nhất trong arsenal của bạn. Đây là nơi bạn lập trình ở tầng factory thay vì tầng application.

Production thực tế: Django's Model, SQLAlchemy's declarative_base, WTForms, pytest fixtures — tất cả xây dựng trên metaclass. Hiểu metaclass nghĩa là hiểu cách các framework này hoạt động từ bên trong, thay vì chỉ sử dụng chúng như hộp đen.

Insight cốt lõi: trong Python, class cũng là object. Giống như class tạo ra instance, metaclass tạo ra class. type là metaclass mặc định — mọi class bạn viết đều là instance của type.


Bức tranh tư duy

Hãy hình dung:

  • Object (instance) = chiếc bánh đã nướng xong
  • Class = khuôn bánh — dùng để tạo hình bánh
  • Metaclass = nhà máy sản xuất khuôn bánh — quyết định khuôn có hình dáng, kích thước, chất liệu gì

Khi bạn viết code bình thường, bạn đang ở trong bếp — lấy khuôn ra và nướng bánh. Khi bạn viết metaclass, bạn đang ở trong nhà máy — thiết kế chính cái khuôn.

Sơ đồ phân cấp:

type (metaclass)      ← nhà máy sản xuất khuôn
  │ tạo ra ↓
MyClass (class)       ← khuôn bánh
  │ tạo ra ↓
my_obj (instance)     ← chiếc bánh
python
class User:
    pass

u = User()
print(type(u))         # <class '__main__.User'>   — u là instance của User
print(type(User))      # <class 'type'>            — User là instance của type
print(type(type))      # <class 'type'>            — type là instance của chính nó

Khi analogy "gãy": metaclass còn có thể can thiệp vào quá trình tạo instance qua __call__. Tức là nhà máy không chỉ sản xuất khuôn, mà còn kiểm soát cách bạn dùng khuôn để nướng bánh — đây là cơ chế đằng sau Singleton pattern.


Cốt lõi kỹ thuật

type — metaclass mặc định

type có hai vai trò hoàn toàn khác nhau:

python
# Vai trò 1 — Kiểm tra kiểu (1 argument)
print(type(42))         # <class 'int'>
print(type("hello"))    # <class 'str'>

# Vai trò 2 — Tạo class động (3 arguments)
# type(name, bases, namespace)
def greet(self):
    return f"Hello, {self.name}"

User = type('User', (), {'name': 'default', 'greet': greet})

# Tương đương 100% với:
class User:
    name = 'default'
    def greet(self):
        return f"Hello, {self.name}"

Mọi class statement là syntactic sugar cho type(name, bases, namespace):

python
class Animal:
    pass

print(isinstance(Animal, type))  # True — Animal là instance của type
print(isinstance(type, type))    # True — type là instance của chính nó

__new__ vs __init__ trong metaclass

Đặc điểm__new____init__
Thời điểmTRƯỚC khi class tồn tạiSAU khi class đã tạo
Tham số đầumcs (metaclass)cls (class vừa tạo)
ReturnPhải return class objectNone
Có thể modifyname, bases, namespaceChỉ cls attributes
Use caseThay đổi cấu trúc classRegistration, logging
python
class TrackerMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Class CHƯA tồn tại — modify namespace trước khi tạo
        namespace['_created_at'] = __import__('time').time()

        if 'to_dict' not in namespace:
            namespace['to_dict'] = lambda self: {
                k: v for k, v in self.__dict__.items()
                if not k.startswith('_')
            }

        # BẮT BUỘC: gọi super().__new__ và return kết quả
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace):
        # Class ĐÃ tồn tại — thích hợp cho logging, registration
        print(f"[META] Class '{name}' created")
        super().__init__(name, bases, namespace)


class Entity(metaclass=TrackerMeta):
    pass
# Output: [META] Class 'Entity' created

Quy tắc nhớ: cần can thiệp cấu trúc class → __new__. Chỉ cần xử lý sau__init__.

__init_subclass__ — lightweight alternative (Python 3.6+)

Hook trên parent class mỗi khi có subclass mới, nhẹ hơn metaclass rất nhiều:

python
class Plugin:
    _registry: dict[str, type] = {}

    def __init_subclass__(cls, *, plugin_name: str = None, **kwargs):
        super().__init_subclass__(**kwargs)
        name = plugin_name or cls.__name__.lower()
        Plugin._registry[name] = cls

    @classmethod
    def get_plugin(cls, name: str):
        plugin_cls = cls._registry.get(name)
        if plugin_cls is None:
            raise KeyError(f"Plugin '{name}' not found")
        return plugin_cls


class AudioPlugin(Plugin, plugin_name="audio"):
    def process(self, data: bytes) -> bytes:
        return data

class VideoPlugin(Plugin, plugin_name="video"):
    def process(self, data: bytes) -> bytes:
        return data

print(Plugin._registry)
# {'audio': <class 'AudioPlugin'>, 'video': <class 'VideoPlugin'>}

Khi nào dùng __init_subclass__ vs metaclass?

Tiêu chí__init_subclass__Full metaclass
Modify bases/namespace trước khi class tạo
Custom __prepare__
Plugin registration✅ ĐủOverkill
ORM field collectionHạn chế✅ Phù hợp
Metaclass conflict riskKhông

Nguyên tắc: bắt đầu với __init_subclass__. Chỉ upgrade lên metaclass khi cần can thiệp sâu.

Class decorators vs Metaclasses

python
def auto_repr(cls):
    """Decorator: tự động tạo __repr__."""
    def __repr__(self):
        attrs = ', '.join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@auto_repr
class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

print(Point(3.0, 4.0))  # Point(x=3.0, y=4.0)
Tiêu chíClass DecoratorMetaclass
Thời điểmSau khi class tạo xongTrong quá trình tạo
Ảnh hưởng subclass❌ Không tự động✅ Tự động kế thừa
Composable✅ Stack nhiều decorator⚠️ Chỉ 1 metaclass
DebuggingDễKhó

Decision tree:

Cần customize class creation?
├── Không → Không cần gì cả
└── Có
    ├── Cần affect tất cả subclasses? → Metaclass / __init_subclass__
    ├── Chỉ thêm method/attribute? → Class decorator hoặc Mixin
    └── Cần modify bases/namespace? → Metaclass (bắt buộc)

ABCMeta và Abstract Base Classes

ABCMeta là metaclass built-in cho interface contracts:

python
from abc import ABC, abstractmethod


class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, amount: float, currency: str) -> str:
        ...

    @abstractmethod
    def refund(self, transaction_id: str) -> bool:
        ...

    def validate_amount(self, amount: float) -> None:
        """Concrete method — subclass kế thừa trực tiếp."""
        if amount <= 0:
            raise ValueError(f"Amount must be positive, got {amount}")


class StripeGateway(PaymentGateway):
    def charge(self, amount: float, currency: str) -> str:
        self.validate_amount(amount)
        return f"stripe_txn_{id(self)}"

    def refund(self, transaction_id: str) -> bool:
        return True


stripe = StripeGateway()  # ✅ OK

# class BadGateway(PaymentGateway):
#     def charge(self, amount, currency): return "txn"
#     # Thiếu refund()
# BadGateway()  → TypeError: Can't instantiate abstract class

Thực chiến

Tình huống 1: ORM Field Collection Metaclass

Xây dựng Model giống Django ORM — metaclass thu thập Field instances, auto-generate __init__, __repr__, to_dict:

python
class Field:
    def __init__(self, field_type: type, required: bool = True, default=None):
        self.field_type = field_type
        self.required = required
        self.default = default
        self.name = None

    def validate(self, value):
        if value is None:
            if self.required:
                raise ValueError(f"Field '{self.name}' is required")
            return self.default
        if not isinstance(value, self.field_type):
            raise TypeError(
                f"Field '{self.name}': expected {self.field_type.__name__}, "
                f"got {type(value).__name__}"
            )
        return value


class StringField(Field):
    def __init__(self, max_length: int = 255, **kwargs):
        super().__init__(str, **kwargs)
        self.max_length = max_length

    def validate(self, value):
        value = super().validate(value)
        if value is not None and len(value) > self.max_length:
            raise ValueError(f"Field '{self.name}': max_length={self.max_length}")
        return value


class IntField(Field):
    def __init__(self, min_value: int = None, max_value: int = None, **kwargs):
        super().__init__(int, **kwargs)
        self.min_value = min_value
        self.max_value = max_value


class ModelMeta(type):
    """Metaclass thu thập Field instances và generate boilerplate."""

    def __new__(mcs, name, bases, namespace):
        fields = {}
        for key, value in list(namespace.items()):
            if isinstance(value, Field):
                value.name = key
                fields[key] = value

        # Kế thừa fields từ parent classes
        for base in bases:
            if hasattr(base, '_fields'):
                for k, v in base._fields.items():
                    fields.setdefault(k, v)

        namespace['_fields'] = fields

        # Auto-generate __init__
        if '__init__' not in namespace and fields:
            def __init__(self, **kwargs):
                for field_name, field in self.__class__._fields.items():
                    raw = kwargs.get(field_name, field.default)
                    setattr(self, field_name, field.validate(raw))
                extra = set(kwargs) - set(self.__class__._fields)
                if extra:
                    raise TypeError(f"Unexpected: {', '.join(extra)}")
            namespace['__init__'] = __init__

        # Auto-generate __repr__
        if '__repr__' not in namespace and fields:
            def __repr__(self):
                attrs = ', '.join(
                    f"{k}={getattr(self, k)!r}" for k in self.__class__._fields
                )
                return f"{self.__class__.__name__}({attrs})"
            namespace['__repr__'] = __repr__

        return super().__new__(mcs, name, bases, namespace)


class Model(metaclass=ModelMeta):
    def to_dict(self) -> dict:
        return {n: getattr(self, n) for n in self.__class__._fields}


# --- Sử dụng ---
class User(Model):
    name = StringField(max_length=100)
    email = StringField(max_length=255)
    age = IntField(min_value=0, required=False)


user = User(name="Nguyen Van A", email="a@example.com", age=28)
print(user)           # User(name='Nguyen Van A', email='a@example.com', age=28)
print(user.to_dict()) # {'name': 'Nguyen Van A', 'email': 'a@example.com', 'age': 28}

# User(email="x")       → ValueError: Field 'name' is required
# User(name="A"*200, email="x") → ValueError: max_length=100

Tình huống 2: Plugin Auto-Registration System

Subclass tự động đăng ký, factory method trả về instance theo tên:

python
class PluginMeta(type):
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)

        if not bases:
            cls._registry = {}
            return cls

        plugin_name = namespace.get('name')
        if plugin_name is None:
            raise TypeError(f"Plugin '{name}' must define 'name' attribute")
        if plugin_name in cls._registry:
            raise ValueError(f"Plugin '{plugin_name}' already registered")

        cls._registry[plugin_name] = cls
        return cls


class BasePlugin(metaclass=PluginMeta):
    name: str

    @classmethod
    def get_plugin(cls, name: str) -> type:
        if name not in cls._registry:
            raise KeyError(f"Plugin '{name}' not found")
        return cls._registry[name]

    @classmethod
    def create(cls, name: str, **kwargs) -> 'BasePlugin':
        return cls.get_plugin(name)(**kwargs)


class JsonPlugin(BasePlugin):
    name = "json"
    def process(self, data) -> str:
        import json
        return json.dumps(data, ensure_ascii=False)

class CsvPlugin(BasePlugin):
    name = "csv"
    def __init__(self, delimiter: str = ","):
        self.delimiter = delimiter
    def process(self, data: list[list]) -> str:
        return "\n".join(self.delimiter.join(str(c) for c in row) for row in data)


print(BasePlugin._registry.keys())  # dict_keys(['json', 'csv'])
serializer = BasePlugin.create("json")
print(serializer.process({"key": "value"}))  # {"key": "value"}

Sai lầm điển hình

Sai lầm 1: Metaclass conflict trong multiple inheritance

python
class MetaA(type): pass
class MetaB(type): pass
class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass

# ❌ SAI — TypeError: metaclass conflict
# class C(A, B): pass
python
# ✅ ĐÚNG — Tạo combined metaclass
class CombinedMeta(MetaA, MetaB): pass
class C(A, B, metaclass=CombinedMeta): pass

Impact: Xảy ra khi kết hợp hai library dùng metaclass khác nhau (vd: Django Model + ABC). Giải pháp: combined metaclass kế thừa cả hai.

Sai lầm 2: Quên return trong __new__

python
# ❌ SAI — class trở thành None
class BrokenMeta(type):
    def __new__(mcs, name, bases, namespace):
        namespace['tag'] = True
        # THIẾU return!
python
# ✅ ĐÚNG
class CorrectMeta(type):
    def __new__(mcs, name, bases, namespace):
        namespace['tag'] = True
        return super().__new__(mcs, name, bases, namespace)

Impact: Class là None. Mọi instantiation crash với TypeError: 'NoneType' is not callable. Traceback không chỉ rõ nguyên nhân.

Sai lầm 3: Modify namespace trong __init__

python
# ❌ SAI — namespace dict đã disconnect khỏi class
class WrongMeta(type):
    def __init__(cls, name, bases, namespace):
        namespace['extra'] = 'value'  # Không có hiệu lực!
        super().__init__(name, bases, namespace)
python
# ✅ ĐÚNG — Modify trong __new__ hoặc set trực tiếp trên cls
class CorrectMeta(type):
    def __new__(mcs, name, bases, namespace):
        namespace['extra'] = 'value'  # ✅ Trước khi class tạo
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace):
        cls.another = 'works'  # ✅ Set trực tiếp trên class
        super().__init__(name, bases, namespace)

Impact: Attribute "biến mất" bí ẩn. Code set rõ ràng nhưng hasattr() return False.

Sai lầm 4: Dùng metaclass khi decorator đủ

python
# ❌ SAI — Over-engineering
class AddReprMeta(type):
    def __new__(mcs, name, bases, namespace):
        namespace['__repr__'] = lambda self: f"{name}({self.__dict__})"
        return super().__new__(mcs, name, bases, namespace)
python
# ✅ ĐÚNG — Decorator hoặc dataclass
from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int
    # __repr__ tự động có!

Impact: Tăng complexity không cần thiết, khó debug, rào cản cho team members. Rule: decorator/__init_subclass__ giải quyết được → dùng chúng. Metaclass là last resort.


Under the Hood

Class creation protocol trong CPython

Khi Python gặp class Foo(Base, metaclass=Meta)::

1. Xác định metaclass (Meta)
2. Meta.__prepare__(name, bases) → trả về namespace dict
3. Execute class body trong namespace
4. Meta.__call__(name, bases, namespace)
   4a. Meta.__new__(mcs, name, bases, namespace) → tạo class
   4b. Meta.__init__(cls, name, bases, namespace) → post-processing
5. Bind class object vào tên 'Foo'
python
class VerboseMeta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        print(f"1. __prepare__('{name}')")
        return super().__prepare__(name, bases, **kwargs)

    def __new__(mcs, name, bases, namespace, **kwargs):
        print(f"2. __new__('{name}')")
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace, **kwargs):
        print(f"3. __init__('{name}')")
        super().__init__(name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        print(f"4. __call__() — creating instance of {cls.__name__}")
        return super().__call__(*args, **kwargs)


class Demo(metaclass=VerboseMeta):
    x = 10
# Output (class definition):  1 → 2 → 3

d = Demo()
# Output (instance creation): 4

type.__call__ simplified

python
# Pseudocode — simplified từ CPython typeobject.c
class type:
    def __call__(cls, *args, **kwargs):
        instance = cls.__new__(cls, *args, **kwargs)
        if isinstance(instance, cls):
            instance.__init__(*args, **kwargs)
        return instance

Đây là lý do Singleton metaclass hoạt động: override __call__ ở tầng metaclass kiểm soát toàn bộ instance creation.

__prepare__ — customize namespace

python
from collections import OrderedDict

class OrderedMeta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        return OrderedDict()

    def __new__(mcs, name, bases, namespace, **kwargs):
        cls = super().__new__(mcs, name, bases, dict(namespace))
        cls._field_order = list(namespace.keys())
        return cls

class Config(metaclass=OrderedMeta):
    host = "localhost"
    port = 8080
    debug = True

print(Config._field_order)
# ['__module__', '__qualname__', 'host', 'port', 'debug']

LƯU Ý

Từ Python 3.7+, dict đã giữ insertion order. __prepare__ vẫn hữu ích khi cần namespace là custom mapping (vd: dict tự validate key).

Metaclass resolution

Khi bases có metaclass khác nhau, Python chọn metaclass "dominant" — là subclass của tất cả metaclasses khác:

python
class Meta1(type): pass
class Meta2(Meta1): pass  # Meta2 kế thừa Meta1

class A(metaclass=Meta1): pass
class B(metaclass=Meta2): pass

class C(A, B): pass
print(type(C))  # <class 'Meta2'> — Meta2 dominant vì là subclass của Meta1

ABCMeta internals

ABCMeta track __abstractmethods__ frozenset. Instance creation bị chặn nếu set không rỗng:

python
from abc import ABCMeta, abstractmethod

class Shape(metaclass=ABCMeta):
    @abstractmethod
    def area(self): ...

print(Shape.__abstractmethods__)  # frozenset({'area'})
# Shape() → TypeError vì __abstractmethods__ không rỗng

__subclasshook__ customize isinstance() check mà không cần kế thừa:

python
from abc import ABC

class Closeable(ABC):
    @classmethod
    def __subclasshook__(cls, C):
        if cls is Closeable and hasattr(C, 'close'):
            return True
        return NotImplemented

class FileWrapper:
    def close(self): pass

print(isinstance(FileWrapper(), Closeable))  # True — nhờ __subclasshook__

Performance

Metaclass overhead xảy ra một lần duy nhất tại class definition (import time), không phải mỗi lần tạo instance. Overhead là negligible — đừng tránh metaclass vì performance, hãy tránh vì complexity.


Checklist ghi nhớ

✅ Checklist triển khai

type và metaclass

  • [ ] type có 2 vai trò: check type (1 arg) và create class (3 args)
  • [ ] Mọi class là instance của type (hoặc custom metaclass)
  • [ ] type(type) trả về type — bootstrap circularity
  • [ ] Custom metaclass kế thừa từ type

__new__ vs __init__ trong metaclass

  • [ ] __new__ chạy TRƯỚC khi class tồn tại — modify name, bases, namespace
  • [ ] __init__ chạy SAU — chỉ set cls attributes trực tiếp
  • [ ] __new__ PHẢI return class object
  • [ ] Modify namespace dict trong __init__ KHÔNG có hiệu lực

Alternatives (khi nào KHÔNG dùng metaclass)

  • [ ] __init_subclass__ đủ cho registration, simple hooks
  • [ ] Class decorator đủ cho thêm methods, modify sau khi tạo
  • [ ] Mixin đủ cho shared behavior
  • [ ] dataclass/attrs đủ cho boilerplate generation
  • [ ] Metaclass chỉ khi cần can thiệp trong quá trình tạo class

Production patterns

  • [ ] ORM field collection: scan namespace cho Field instances
  • [ ] Plugin registry: __init_subclass__ hoặc metaclass auto-register
  • [ ] Singleton: metaclass override __call__
  • [ ] Interface enforcement: ABCMeta + @abstractmethod
  • [ ] Metaclass conflict: combined metaclass kế thừa cả hai

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

Bài 1: Singleton Metaclass (Intermediate)

Implement SingletonMeta — mỗi class chỉ có đúng 1 instance. Hỗ trợ reset() cho testing.

python
class SingletonMeta(type):
    # TODO: implement

class Database(metaclass=SingletonMeta):
    def __init__(self, url: str):
        self.url = url

# Test
db1 = Database("postgres://localhost")
db2 = Database("mysql://localhost")
assert db1 is db2
assert db1.url == "postgres://localhost"

Database.reset()
db3 = Database("sqlite://memory")
assert db3 is not db1
💡 Lời giải Bài 1
python
class SingletonMeta(type):
    _instances: dict = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

    def reset(cls):
        """Xóa singleton instance — hữu ích cho testing."""
        cls._instances.pop(cls, None)


class Database(metaclass=SingletonMeta):
    def __init__(self, url: str):
        self.url = url
        print(f"Connecting to {url}...")

db1 = Database("postgres://localhost")   # Connecting to postgres://localhost...
db2 = Database("mysql://localhost")       # Không in gì — reuse
print(db1 is db2)   # True
print(db1.url)       # postgres://localhost

Database.reset()
db3 = Database("sqlite://memory")  # Connecting to sqlite://memory...
print(db3 is db1)   # False

Giải thích: __call__ trên metaclass chạy mỗi khi gọi Database(...). Override nó để check cache trước khi tạo instance mới.

Bài 2: ValidatedModel Metaclass (Advanced)

Implement metaclass ép buộc subclass phải khai báo tất cả required class attributes:

python
class ValidatedModelMeta(type):
    # TODO: implement

class APIEndpoint(metaclass=ValidatedModelMeta):
    _required_attrs = ['method', 'path', 'handler']

class UserEndpoint(APIEndpoint):
    method = 'GET'
    path = '/users'
    handler = 'get_users'  # ✅ OK

# class BadEndpoint(APIEndpoint):
#     method = 'POST'
#     # → TypeError tại class definition
💡 Lời giải Bài 2
python
class ValidatedModelMeta(type):
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if not bases:
            return cls

        required = set()
        for base in cls.__mro__:
            if '_required_attrs' in base.__dict__:
                required.update(base._required_attrs)

        missing = sorted(
            attr for attr in required
            if attr not in namespace
            and not any(attr in b.__dict__ for b in bases)
        )

        if missing:
            raise TypeError(
                f"Class '{name}' missing required: {', '.join(missing)}"
            )
        return cls


class APIEndpoint(metaclass=ValidatedModelMeta):
    _required_attrs = ['method', 'path', 'handler']

class UserEndpoint(APIEndpoint):
    method = 'GET'
    path = '/api/users'
    handler = 'list_users'

print(f"Created: {UserEndpoint.path}")  # /api/users

Giải thích: __new__ validate tại class creation — lỗi phát hiện ngay khi import, không phải runtime.

🧠 Quiz

Câu hỏi: Thứ tự thực thi khi định nghĩa class với metaclass

python
class Meta(type):
    def __new__(mcs, name, bases, ns):
        print("A")
        return super().__new__(mcs, name, bases, ns)
    def __init__(cls, name, bases, ns):
        print("B")
    def __call__(cls, *args, **kwargs):
        print("C")
        return super().__call__(*args, **kwargs)

class Foo(metaclass=Meta):
    def __init__(self):
        print("D")

f = Foo()

Output là gì?

  • [ ] A → B → C → D
  • [ ] C → A → B → D
  • [ ] A → B → D → C
  • [ ] A → C → B → D

Đáp án: A → B → C → D

  • A (Meta.__new__): chạy khi class Foo được tạo
  • B (Meta.__init__): post-processing class Foo
  • C (Meta.__call__): chạy khi gọi Foo()
  • D (Foo.__init__): khởi tạo instance bên trong super().__call__()

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