Skip to content

Metaclasses Expert

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

Learning Outcomes

Sau khi hoàn thành trang này, bạn sẽ:

  • ✅ Hiểu type() function và cách Python tạo classes
  • ✅ Phân biệt __new__ vs __init__ trong metaclasses
  • ✅ So sánh Class Decorators vs Metaclasses
  • ✅ Implement metaclasses cho validation, registration, và ORMs
  • ✅ Biết khi nào KHÔNG nên dùng metaclasses
  • ✅ Tránh các production pitfalls phổ biến

type() - Nền tảng của Metaclasses

Trong Python, mọi thứ đều là object, kể cả classes. Và type là metaclass mặc định của tất cả classes.

python
# type() có 2 cách dùng:

# 1. Kiểm tra type của object
print(type(42))        # <class 'int'>
print(type("hello"))   # <class 'str'>

# 2. Tạo class động
class User:
    name = "default"
    
    def greet(self):
        return f"Hello, {self.name}"

# Tương đương với:
User = type(
    'User',                          # Tên class
    (),                              # Base classes (tuple)
    {                                # Namespace (dict)
        'name': 'default',
        'greet': lambda self: f"Hello, {self.name}"
    }
)

# Cả hai cách đều tạo ra class giống hệt nhau!

Class là Instance của type

python
class MyClass:
    pass

# MyClass là instance của type
print(type(MyClass))           # <class 'type'>
print(isinstance(MyClass, type))  # True

# Và type là instance của chính nó!
print(type(type))              # <class 'type'>

Hierarchy

type (metaclass)
  ↓ creates
MyClass (class)
  ↓ creates
my_instance (object)

Tạo Custom Metaclass

Metaclass là class kế thừa từ type.

python
class MyMeta(type):
    """Custom metaclass."""
    
    def __new__(mcs, name, bases, namespace):
        """
        Được gọi TRƯỚC khi class được tạo.
        
        Args:
            mcs: Metaclass (MyMeta)
            name: Tên class đang được tạo
            bases: Tuple các base classes
            namespace: Dict chứa attributes và methods
        
        Returns:
            Class object mới
        """
        print(f"Creating class: {name}")
        print(f"Bases: {bases}")
        print(f"Namespace keys: {list(namespace.keys())}")
        
        # Tạo class bằng cách gọi type.__new__
        cls = super().__new__(mcs, name, bases, namespace)
        return cls
    
    def __init__(cls, name, bases, namespace):
        """
        Được gọi SAU khi class đã được tạo.
        
        Args:
            cls: Class vừa được tạo
            name, bases, namespace: Giống __new__
        """
        print(f"Initializing class: {name}")
        super().__init__(name, bases, namespace)

# Sử dụng metaclass
class User(metaclass=MyMeta):
    name = "default"
    
    def greet(self):
        return f"Hello, {self.name}"

# Output:
# Creating class: User
# Bases: ()
# Namespace keys: ['__module__', '__qualname__', 'name', 'greet']
# Initializing class: User

__new__ vs __init__ trong Metaclass

MethodKhi nào gọiMục đíchReturn
__new__Trước khi class tồn tạiTạo và customize classClass object
__init__Sau khi class đã tạoPost-processingNone
python
class Meta(type):
    def __new__(mcs, name, bases, namespace):
        # Có thể modify namespace TRƯỚC khi class được tạo
        namespace['created_by'] = 'Meta.__new__'
        
        # Có thể thay đổi bases
        # bases = bases + (SomeMixin,)
        
        # Có thể thay đổi name
        # name = 'Modified' + name
        
        return super().__new__(mcs, name, bases, namespace)
    
    def __init__(cls, name, bases, namespace):
        # Class đã tồn tại, chỉ có thể thêm attributes
        cls.initialized_by = 'Meta.__init__'
        super().__init__(name, bases, namespace)

class MyClass(metaclass=Meta):
    pass

print(MyClass.created_by)      # 'Meta.__new__'
print(MyClass.initialized_by)  # 'Meta.__init__'

Use Case 1: Auto-Registration

python
class PluginMeta(type):
    """Metaclass tự động đăng ký plugins."""
    
    _registry: dict[str, type] = {}
    
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        
        # Không đăng ký base class
        if bases:  # Chỉ đăng ký subclasses
            plugin_name = namespace.get('name', name.lower())
            mcs._registry[plugin_name] = cls
            print(f"Registered plugin: {plugin_name}")
        
        return cls
    
    @classmethod
    def get_plugin(mcs, name: str):
        return mcs._registry.get(name)
    
    @classmethod
    def list_plugins(mcs):
        return list(mcs._registry.keys())

class Plugin(metaclass=PluginMeta):
    """Base class cho plugins."""
    name: str = ""

class AudioPlugin(Plugin):
    name = "audio"
    
    def process(self):
        return "Processing audio..."

class VideoPlugin(Plugin):
    name = "video"
    
    def process(self):
        return "Processing video..."

# Output:
# Registered plugin: audio
# Registered plugin: video

print(PluginMeta.list_plugins())  # ['audio', 'video']
print(PluginMeta.get_plugin('audio'))  # <class 'AudioPlugin'>

Use Case 2: Attribute Validation

python
class ValidatedMeta(type):
    """Metaclass kiểm tra class có đủ required attributes."""
    
    required_attrs = ['name', 'version']
    
    def __new__(mcs, name, bases, namespace):
        # Bỏ qua base class
        if bases:
            for attr in mcs.required_attrs:
                if attr not in namespace:
                    raise TypeError(
                        f"Class {name} missing required attribute: {attr}"
                    )
        
        return super().__new__(mcs, name, bases, namespace)

class Component(metaclass=ValidatedMeta):
    """Base class yêu cầu name và version."""
    pass

class Button(Component):
    name = "Button"
    version = "1.0.0"
    
    def render(self):
        return f"<button>{self.name}</button>"

# ❌ TypeError: Class BadComponent missing required attribute: version
class BadComponent(Component):
    name = "Bad"

Use Case 3: ORM-Style Field Collection

python
class Field:
    """Base class cho ORM fields."""
    
    def __init__(self, field_type: type, required: bool = True):
        self.field_type = field_type
        self.required = required
        self.name = None  # Set by metaclass
    
    def validate(self, value):
        if value is None and self.required:
            raise ValueError(f"{self.name} is required")
        if value is not None and not isinstance(value, self.field_type):
            raise TypeError(
                f"{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 and len(value) > self.max_length:
            raise ValueError(
                f"{self.name}: max length is {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 Fields và tạo __init__."""
    
    def __new__(mcs, name, bases, namespace):
        # Thu thập fields
        fields = {}
        for key, value in list(namespace.items()):
            if isinstance(value, Field):
                value.name = key
                fields[key] = value
        
        namespace['_fields'] = fields
        
        # Tạo __init__ tự động
        def __init__(self, **kwargs):
            for field_name, field in self._fields.items():
                value = kwargs.get(field_name)
                validated = field.validate(value)
                setattr(self, field_name, validated)
        
        namespace['__init__'] = __init__
        
        return super().__new__(mcs, name, bases, namespace)

class Model(metaclass=ModelMeta):
    """Base class cho ORM models."""
    
    def to_dict(self):
        return {name: getattr(self, name) for name in self._fields}

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

user = User(name="HPN", email="hpn@example.com", age=28)
print(user.to_dict())  # {'name': 'HPN', 'age': 28, 'email': 'hpn@example.com'}

# Validation hoạt động
User(name="", email="test@test.com")  # OK
User(name="A" * 200, email="x")       # ValueError: name: max length is 100
User(email="test@test.com")           # ValueError: name is required

Class Decorators vs Metaclasses

Khi nào dùng Class Decorator

python
def add_repr(cls):
    """Class decorator thêm __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

@add_repr
class User:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

print(User("HPN", 28))  # User(name='HPN', age=28)

Khi nào dùng Metaclass

python
class SingletonMeta(type):
    """Metaclass đảm bảo chỉ có 1 instance."""
    
    _instances: dict = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

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

db1 = Database("postgresql://localhost")  # Connecting...
db2 = Database("mysql://localhost")       # Không in gì - dùng instance cũ
print(db1 is db2)  # True
))

So sánh

Tiêu chíClass DecoratorMetaclass
Độ phức tạpĐơn giảnPhức tạp
Kế thừaKhông tự độngTự động kế thừa
Thời điểmSau khi class tạoTrong quá trình tạo
Use caseThêm methods, modifyControl class creation
DebuggingDễKhó

Quy tắc chọn

python
# ✅ Dùng Class Decorator khi:
# - Thêm/modify methods hoặc attributes
# - Không cần affect subclasses
# - Logic đơn giản

# ✅ Dùng Metaclass khi:
# - Cần control class creation
# - Cần affect tất cả subclasses
# - Cần modify class TRƯỚC khi nó tồn tại
# - Implementing frameworks (ORMs, validation)

# ✅ Dùng __init_subclass__ khi:
# - Cần hook vào subclass creation
# - Không cần full metaclass power
# - Python 3.6+

__init_subclass__ - Alternative đơn giản

Python 3.6+ cung cấp __init_subclass__ như alternative nhẹ hơn metaclass.

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

class AudioPlugin(Plugin, plugin_name="audio"):
    pass

class VideoPlugin(Plugin):  # Dùng tên class
    pass

print(Plugin._registry)  # {'audio': AudioPlugin, 'videoplug': VideoPlugin}

So sánh với Metaclass

python
# Metaclass - Mạnh hơn nhưng phức tạp
class PluginMeta(type):
    _registry = {}
    
    def __new__(mcs, name, bases, namespace, plugin_name=None, **kwargs):
        cls = super().__new__(mcs, name, bases, namespace, **kwargs)
        if bases:
            mcs._registry[plugin_name or name.lower()] = cls
        return cls

# __init_subclass__ - Đơn giản hơn, đủ cho hầu hết cases
class Plugin:
    _registry = {}
    
    def __init_subclass__(cls, plugin_name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        Plugin._registry[plugin_name or cls.__name__.lower()] = cls

Production Pitfalls 🚨

Pitfall 1: Metaclass Conflict

python
class Meta1(type):
    pass

class Meta2(type):
    pass

class A(metaclass=Meta1):
    pass

class B(metaclass=Meta2):
    pass

# ❌ TypeError: metaclass conflict
class C(A, B):
    pass

# ✅ FIX: Tạo metaclass kế thừa cả hai
class CombinedMeta(Meta1, Meta2):
    pass

class C(A, B, metaclass=CombinedMeta):
    pass

Pitfall 2: Quên gọi super()

python
# ❌ BUG: Không gọi super().__new__
class BadMeta(type):
    def __new__(mcs, name, bases, namespace):
        namespace['added'] = True
        # Quên return super().__new__()!
        # Return None → class không được tạo

# ✅ FIX: Luôn gọi super()
class GoodMeta(type):
    def __new__(mcs, name, bases, namespace):
        namespace['added'] = True
        return super().__new__(mcs, name, bases, namespace)

Pitfall 3: Modify namespace sau khi class tạo

python
# ❌ BUG: Modify namespace trong __init__ không có effect
class BadMeta(type):
    def __init__(cls, name, bases, namespace):
        namespace['late_add'] = True  # Không có effect!
        super().__init__(name, bases, namespace)

class Test(metaclass=BadMeta):
    pass

print(hasattr(Test, 'late_add'))  # False!

# ✅ FIX: Modify trong __new__ hoặc set trực tiếp trên cls
class GoodMeta(type):
    def __init__(cls, name, bases, namespace):
        cls.late_add = True  # Set trực tiếp trên class
        super().__init__(name, bases, namespace)

Pitfall 4: Infinite recursion với __call__

python
# ❌ BUG: Infinite recursion
class BadMeta(type):
    def __call__(cls, *args, **kwargs):
        print("Creating instance...")
        return cls(*args, **kwargs)  # Gọi lại __call__!

# ✅ FIX: Gọi super().__call__
class GoodMeta(type):
    def __call__(cls, *args, **kwargs):
        print("Creating instance...")
        return super().__call__(*args, **kwargs)

Pitfall 5: Overcomplicating với Metaclass

python
# ❌ OVERKILL: Dùng metaclass cho việc đơn giản
class AddReprMeta(type):
    def __new__(mcs, name, bases, namespace):
        def __repr__(self):
            return f"{name}(...)"
        namespace['__repr__'] = __repr__
        return super().__new__(mcs, name, bases, namespace)

# ✅ BETTER: Dùng class decorator
def add_repr(cls):
    cls.__repr__ = lambda self: f"{cls.__name__}(...)"
    return cls

# ✅ EVEN BETTER: Dùng dataclass
from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int
# __repr__ được tạo tự động!

Khi nào KHÔNG dùng Metaclass

🚨 TRÁNH METACLASS KHI

  1. Class decorator đủ dùng - Đơn giản hơn nhiều
  2. __init_subclass__ đủ dùng - Python 3.6+ alternative
  3. Chỉ cần thêm methods - Dùng mixin hoặc decorator
  4. Team không quen - Metaclass khó debug và maintain
  5. Không có use case rõ ràng - "Cool" không phải lý do

Decision Tree

Cần customize class creation?
├── Không → Không cần metaclass
└── Có → Cần affect subclasses?
    ├── Không → Dùng class decorator
    └── Có → Cần modify TRƯỚC khi class tồn tại?
        ├── Không → Dùng __init_subclass__
        └── Có → Dùng metaclass

Bảng Tóm tắt

python
# === TẠO CLASS ĐỘNG ===
MyClass = type('MyClass', (Base,), {'attr': value})

# === CUSTOM METACLASS ===
class MyMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Modify trước khi class tạo
        return super().__new__(mcs, name, bases, namespace)
    
    def __init__(cls, name, bases, namespace):
        # Post-processing sau khi class tạo
        super().__init__(name, bases, namespace)
    
    def __call__(cls, *args, **kwargs):
        # Control instance creation
        return super().__call__(*args, **kwargs)

class MyClass(metaclass=MyMeta):
    pass

# === ALTERNATIVE: __init_subclass__ ===
class Base:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        # Hook vào subclass creation

# === ALTERNATIVE: Class Decorator ===
def decorator(cls):
    cls.added = True
    return cls

@decorator
class MyClass:
    pass