Giao diện
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ánh1
2
3
4
5
2
3
4
5
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ó1
2
3
4
5
6
7
2
3
4
5
6
7
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}"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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ó1
2
3
4
5
2
3
4
5
__new__ vs __init__ trong metaclass
| Đặc điểm | __new__ | __init__ |
|---|---|---|
| Thời điểm | TRƯỚC khi class tồn tại | SAU khi class đã tạo |
| Tham số đầu | mcs (metaclass) | cls (class vừa tạo) |
| Return | Phải return class object | None |
| Có thể modify | name, bases, namespace | Chỉ cls attributes |
| Use case | Thay đổi cấu trúc class | Registration, 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' created1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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'>}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 collection | Hạn chế | ✅ Phù hợp |
| Metaclass conflict risk | Không | Có |
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)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| Tiêu chí | Class Decorator | Metaclass |
|---|---|---|
| Thời điểm | Sau khi class tạo xong | Trong 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 |
| Debugging | Dễ | 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)1
2
3
4
5
6
2
3
4
5
6
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 class1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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=1001
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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"}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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): pass1
2
3
4
5
6
7
2
3
4
5
6
7
python
# ✅ ĐÚNG — Tạo combined metaclass
class CombinedMeta(MetaA, MetaB): pass
class C(A, B, metaclass=CombinedMeta): pass1
2
3
2
3
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!1
2
3
4
5
2
3
4
5
python
# ✅ ĐÚNG
class CorrectMeta(type):
def __new__(mcs, name, bases, namespace):
namespace['tag'] = True
return super().__new__(mcs, name, bases, namespace)1
2
3
4
5
2
3
4
5
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)1
2
3
4
5
2
3
4
5
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)1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
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)1
2
3
4
5
2
3
4
5
python
# ✅ ĐÚNG — Decorator hoặc dataclass
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
# __repr__ tự động có!1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
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'1
2
3
4
5
6
7
2
3
4
5
6
7
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): 41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 instance1
2
3
4
5
6
7
2
3
4
5
6
7
Đâ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']1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 Meta11
2
3
4
5
6
7
8
2
3
4
5
6
7
8
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ỗng1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
__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__1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
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
- [ ]
typecó 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 db11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
💡 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) # False1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 definition1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
💡 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/users1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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ạoB(Meta.__init__): post-processing class FooC(Meta.__call__): chạy khi gọiFoo()D(Foo.__init__): khởi tạo instance bên trongsuper().__call__()
Liên kết học tiếp
📚 Bài liên quan
Kiến thức nền tảng:
- Classes & Objects — Nền tảng OOP — hiểu class trước khi hiểu metaclass
- Inheritance & MRO — cách Python resolve method và metaclass trong hierarchy
- Descriptors —
__set_name__,__get__,__set__— nền tảng cho ORM fields
Kiến thức nâng cao:
- Protocols & Structural Subtyping — alternative hiện đại cho ABCMeta
- Decorators nâng cao — class decorators chi tiết
Glossary: metaclass, type, __new__, __init__, __prepare__, __init_subclass__, ABCMeta, __abstractmethods__, __subclasshook__
Search aliases: "python metaclass tutorial", "type metaclass python", "ORM metaclass pattern", "singleton metaclass", "ABCMeta vs Protocol"