Day 046 — 元类(Metaclass)
Phase 4 — 高阶特性 | Day 046/200 | 实战日
📌 今日目标
理解 Python 中"一切皆对象"的终极体现——元类。掌握 type() 的本质、动态创建类的方法,以及如何用自定义元类实现 ORM 字段验证。
一、类也是对象
1.1 核心概念
在 Python 中,一切皆对象。整数是对象,字符串是对象,函数是对象——类本身也是对象。
class Dog:
pass
# Dog 是一个类,但它同时也是一个对象
print(type(Dog)) # <class 'type'>
print(isinstance(Dog, type)) # True
这意味着:
- 类可以赋值给变量
- 类可以作为参数传递
- 类可以动态创建
- 类有类型——它的类型就是 type
1.2 为什么类是对象?
Python 的设计哲学是一致性。如果函数可以作为对象传递(回调机制),那么类也应该是对象。这使得:
- 元编程成为可能——你可以编写"生成类的代码"
- 框架设计更加灵活——Django ORM、SQLAlchemy 都依赖此机制
- 装饰器可以应用于类——
@dataclass、@singleton等
1.3 对象的层级关系
普通对象(实例) → 类(Class) → 元类(Metaclass)
Dog() class Dog: type
实例 类定义 创建类的"类"
每个对象都有一个 type:
- type(dog_instance) → <class 'Dog'>
- type(Dog) → <class 'type'>
- type(type) → <class 'type'>(type 是它自己的实例!)
二、type() 动态创建类
2.1 type() 的两种用法
用法一:查询对象类型
x = 42
print(type(x)) # <class 'int'>
用法二:动态创建类
# type(类名, 基类元组, 属性字典)
MyClass = type('MyClass', (object,), {'greet': lambda self: 'Hello!'})
obj = MyClass()
print(obj.greet()) # Hello!
type() 三参数版本等价于:
class MyClass:
def greet(self):
return 'Hello!'
2.2 type() 的完整参数
| 参数 | 说明 | 示例 |
|---|---|---|
name |
类名(字符串) | 'Dog' |
bases |
基类元组,没有就写 (object,) |
(Animal,) |
attrs |
类属性字典 | {'legs': 4} |
2.3 动态创建类的实际意义
# 根据配置文件动态生成类
config = {
'name': 'User',
'fields': ['name', 'email', 'age']
}
# 动态创建一个包含验证方法的类
def make_validator(fields):
attrs = {f: None for f in fields}
attrs['validate'] = lambda self: all(getattr(self, f) is not None for f in fields)
return type('Model', (), attrs)
Model = make_validator(config['fields'])
user = Model()
user.name = 'Alice'
user.email = 'alice@example.com'
user.age = 30
print(user.validate()) # True
三、自定义元类
3.1 元类是什么?
元类就是创建类的类。当你写 class Dog: 时,Python 会:
- 执行
type()来创建Dog类对象 - 但如果有自定义元类,Python 会用自定义元类来创建
3.2 编写第一个自定义元类
class UpperAttrMetaclass(type):
"""自动将类的所有属性名转为大写"""
def __new__(mcs, name, bases, attrs):
uppercase_attrs = {}
for key, value in attrs.items():
if not key.startswith('__'):
uppercase_attrs[key.upper()] = value
else:
uppercase_attrs[key] = value
return super().__new__(mcs, name, bases, uppercase_attrs)
class MyClass(metaclass=UpperAttrMetaclass):
name = 'test'
print(dir(MyClass)) # 包含 NAME 而不是 name
3.3 元类的关键方法
| 方法 | 何时调用 | 用途 |
|---|---|---|
__new__(mcs, name, bases, attrs) |
创建类时 | 控制类的创建过程 |
__init__(cls, name, bases, attrs) |
创建类后 | 初始化类 |
__call__(cls, *args, **kwargs) |
创建实例时 | 控制实例化过程 |
3.4 元类 vs 装饰器
| 特性 | 元类 | 装饰器 |
|---|---|---|
| 作用范围 | 影响所有子类 | 仅影响被装饰的类 |
| 复杂度 | 高 | 低 |
| 可读性 | 差 | 好 |
| 适用场景 | 框架、库设计 | 简单的类修改 |
经验法则:能用装饰器解决的,就不用元类。
四、实战:ORM 字段验证
4.1 需求分析
仿照 Django ORM,实现一个简单的字段验证系统:
- 定义字段类型(StringField、IntegerField 等)
- 自动收集字段定义
- 创建实例时自动验证
4.2 实现方案
使用元类实现:
class Field:
def __init__(self, required=True, max_length=None):
self.required = required
self.max_length = max_length
self.name = None
class StringField(Field):
pass
class IntegerField(Field):
pass
class ModelMeta(type):
def __new__(mcs, name, bases, attrs):
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
value.name = key
fields[key] = value
attrs['_fields'] = fields
return super().__new__(mcs, name, bases, attrs)
class Model(metaclass=ModelMeta):
def __init__(self, **kwargs):
for name, field in self._fields.items():
if field.required and name not in kwargs:
raise ValueError(f"缺少必填字段: {name}")
setattr(self, name, kwargs.get(name))
def validate(self):
for name, field in self._fields.items():
value = getattr(self, name, None)
if value is not None and field.max_length:
if len(str(value)) > field.max_length:
raise ValueError(f"{name} 超出最大长度 {field.max_length}")
# 使用
class User(Model):
name = StringField(required=True, max_length=50)
age = IntegerField(required=True)
user = User(name='Alice', age=30)
user.validate() # 通过
五、API 速查表
type() 函数
# 查询类型
type(obj) # 返回 obj 的类型
type(obj) is SomeClass # 判断类型
# 动态创建类
MyClass = type('MyClass', (BaseClass,), {'attr': value})
自定义元类
class Meta(type):
def __new__(mcs, name, bases, attrs):
# mcs: 元类自身
# name: 类名
# bases: 基类元组
# attrs: 类属性字典
return super().__new__(mcs, name, bases, attrs)
def __init__(cls, name, bases, attrs):
super().__init__(name, bases, attrs)
def __call__(cls, *args, **kwargs):
return super().__call__(*args, **kwargs)
# 使用
class MyClass(metaclass=Meta):
pass
hasattr / getattr 动态操作
# 元类常与动态属性操作配合
hasattr(cls, 'some_attr') # 检查属性是否存在
getattr(cls, 'some_attr', None) # 安全获取属性
setattr(cls, 'new_attr', value) # 动态设置属性
六、原理解析图
元类调用链
用户代码: class Dog(Animal):
legs = 4
def bark(self): ...
↓ Python 解释器
type('Dog', (Animal,), {'legs': 4, 'bark': <function>})
↓
type.__call__()
↓
type.__new__() ← 创建 Dog 类对象
↓
type.__init__() ← 初始化 Dog 类
↓
Dog 类对象诞生 🎉
自定义元类调用链
用户代码: class Dog(Animal, metaclass=Meta):
legs = 4
def bark(self): ...
↓ Python 解释器
Meta.__new__(Meta, 'Dog', (Animal,), {'legs': 4, 'bark': ...})
↓
Meta 创建 Dog 类(可以修改 attrs)
↓
Meta.__init__(Dog, ...)
↓
Dog 类对象诞生 🎉
七、实战代码案例
案例 1:自动注册类到注册表
registry = {}
class PluginMeta(type):
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
if bases: # 不注册基类本身
registry[name] = cls
return cls
class Plugin(metaclass=PluginMeta):
pass
class MathPlugin(Plugin):
def run(self):
return 'Math running'
class DataPlugin(Plugin):
def run(self):
return 'Data running'
print(registry)
# {'MathPlugin': <class 'MathPlugin'>, 'DataPlugin': <class 'DataPlugin'>}
案例 2:单例模式
class SingletonMeta(type):
_instances = {}
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):
self.connection = 'Connected'
db1 = Database()
db2 = Database()
print(db1 is db2) # True
案例 3:自动添加 repr
class AutoReprMeta(type):
def __new__(mcs, name, bases, attrs):
fields = [k for k, v in attrs.items()
if not k.startswith('_') and not callable(v)]
def __repr__(self):
values = ', '.join(f'{f}={getattr(self, f)!r}' for f in fields)
return f'{name}({values})'
attrs['__repr__'] = __repr__
return super().__new__(mcs, name, bases, attrs)
class Point(metaclass=AutoReprMeta):
x = 0
y = 0
p = Point()
p.x = 3
p.y = 4
print(p) # Point(x=3, y=4)
八、思考题
-
为什么
type(type) is type? 这意味着什么? -
元类的
__new__和__init__有什么区别? 在元类中,__new__不能直接调用super().__init__(),为什么? -
如果一个类同时使用了元类和装饰器,执行顺序是什么? 先元类还是先装饰器?
-
Django 的
Model基类是如何利用元类实现字段自动收集的? 试着查看 Django 源码中的ModelMetaclass。 -
能否用元类实现
@dataclass的功能? 如果能,怎么做?如果不能,为什么?
📚 延伸阅读
上一课:Day 045 — OOP 综合实战
下一课:Day 047 — 描述符(Descriptor)