元类
00:00
元类与类创建过程
概述
元类(Metaclass)是 Python 中用于创建类的类。普通类创建实例对象,而元类创建类对象。理解元类需要先理解 Python 的类创建机制:类本身也是对象,是 type 的实例。元类允许我们在类创建时拦截和修改类的定义过程,实现诸如自动注册、字段验证、接口检查等高级功能。
基础概念
类也是对象
在 Python 中,类本身也是对象。class 语句本质上是一个语法糖,它调用 type 来创建类:
# 使用 class 关键字
class MyClass:
x = 10
# 等价于使用 type 动态创建
MyClass = type('MyClass', (), {'x': 10})
type 的三个参数:
- 类名(字符串)
- 基类元组
- 属性/方法字典
元类的层次
type 创建 MyClass
MyClass 创建 my_instance
type 是 MyClass 的元类
type 是 type 自身的元类(自举)
元类的触发时机
元类在类定义被解析时调用,而不是在实例化时调用。这意味着元类的修改在类创建时就生效了。
快速上手
自定义元类
class Meta(type):
"""自定义元类"""
def __new__(mcs, name, bases, namespace):
# 修改类创建过程
namespace['class_id'] = id(mcs)
return super().__new__(mcs, name, bases, namespace)
class MyClass(metaclass=Meta):
pass
obj = MyClass()
print(obj.class_id) # 自动添加了 class_id 属性
new vs init vs call
元类有三个可以重写的方法:
class Meta(type):
def __new__(mcs, name, bases, namespace):
"""创建类对象(最先调用)"""
print(f"Meta.__new__ 创建类: {name}")
return super().__new__(mcs, name, bases, namespace)
def __init__(cls, name, bases, namespace):
"""初始化类对象(__new__ 之后调用)"""
print(f"Meta.__init__ 初始化类: {name}")
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
"""控制实例创建过程"""
print(f"Meta.__call__ 创建实例: {cls.__name__}")
return super().__call__(*args, **kwargs)
class MyClass(metaclass=Meta):
pass
# 输出:
# Meta.__new__ 创建类: MyClass
# Meta.__init__ 初始化类: MyClass
obj = MyClass() # 输出: Meta.__call__ 创建实例: MyClass
详细用法
自动注册模式
class RegistryMeta(type):
"""自动注册子类的元类"""
_registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# 不注册基类本身
if bases and bases[0] is not object:
mcs._registry[name.lower()] = cls
return cls
class Plugin(metaclass=RegistryMeta):
"""插件基类"""
def execute(self):
raise NotImplementedError
class MySQLPlugin(Plugin):
def execute(self):
return "MySQL 执行"
class RedisPlugin(Plugin):
def execute(self):
return "Redis 执行"
# 自动注册
print(RegistryMeta._registry)
# {'mysqlplugin': <class 'MySQLPlugin'>, 'redisplugin': <class 'RedisPlugin'>}
字段验证
class ValidatedMeta(type):
"""验证类属性的元类"""
def __new__(mcs, name, bases, namespace):
# 验证所有公开属性必须是简单类型
for key, value in namespace.items():
if key.startswith('_'):
continue
if not isinstance(value, (int, str, float, bool)):
raise TypeError(f"{key} 必须是简单类型,实际是 {type(value).__name__}")
return super().__new__(mcs, name, bases, namespace)
class Config(metaclass=ValidatedMeta):
host = "localhost"
port = 8080
debug = False
# tags = [] # TypeError: tags 必须是简单类型,实际是 list
单例元类
class Singleton(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=Singleton):
def __init__(self):
print("初始化数据库连接")
self.connection = "connected"
# 多次实例化返回同一对象
db1 = Database() # 初始化数据库连接
db2 = Database() # 不会再次初始化
print(db1 is db2) # True
接口检查
class InterfaceMeta(type):
"""接口元类:检查子类是否实现了所有抽象方法"""
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# 基类本身不需要检查
if not bases or bases == (object,):
return cls
# 收集所有需要实现的抽象方法
abstract_methods = set()
for base in bases:
abstract_methods.update(getattr(base, '_abstract_methods', set()))
# 检查是否全部实现
for method in abstract_methods:
if method not in namespace:
raise TypeError(f"{name} 未实现抽象方法: {method}")
return cls
class Animal(metaclass=InterfaceMeta):
_abstract_methods = {'speak', 'move'}
class Dog(Animal):
def speak(self):
return "汪汪"
def move(self):
return "跑"
# class Cat(Animal): # TypeError: Cat 未实现抽象方法: speak, move
# pass
常见场景
场景一:ORM 字段映射
class Field:
"""数据库字段描述符"""
def __init__(self, field_type, column_name=None):
self.field_type = field_type
self.column_name = column_name
def __set_name__(self, owner, name):
if self.column_name is None:
self.column_name = name
class ModelMeta(type):
"""ORM 模型元类"""
def __new__(mcs, name, bases, namespace):
fields = {}
for key, value in namespace.items():
if isinstance(value, Field):
fields[key] = value
namespace['_fields'] = fields
return super().__new__(mcs, name, bases, namespace)
class Model(metaclass=ModelMeta):
pass
class User(Model):
id = Field(int, 'user_id')
name = Field(str)
age = Field(int)
print(User._fields) # {'id': Field(...), 'name': Field(...), 'age': Field(...)}
场景二:方法自动绑定
class EventMeta(type):
"""自动注册事件处理器的元类"""
def __new__(mcs, name, bases, namespace):
handlers = {}
for key, value in namespace.items():
if key.startswith('on_'):
event_name = key[3:] # 去掉 on_ 前缀
handlers[event_name] = value
namespace['_handlers'] = handlers
return super().__new__(mcs, name, bases, namespace)
class EventHandler(metaclass=EventMeta):
def dispatch(self, event_name, *args, **kwargs):
handler = self._handlers.get(event_name)
if handler:
return handler(self, *args, **kwargs)
class MyHandler(EventHandler):
def on_click(self, button):
print(f"点击了: {button}")
def on_keypress(self, key):
print(f"按下了: {key}")
h = MyHandler()
h.dispatch("click", "提交") # 点击了: 提交
注意事项
- 元类代码复杂,容易出错,应优先考虑其他方案(装饰器、init_subclass、类装饰器)
- Python 之禅:“元类是 99% 的程序员都不需要关心的深奥魔法”
- 多个元类冲突时需要解决 MRO(方法解析顺序)问题
- 元类会影响所有子类,修改时需要考虑继承链上的影响
- 调试元类代码比较困难,因为类创建发生在导入时
进阶用法
init_subclass 替代简单元类
Python 3.6 引入的 __init_subclass__ 可以替代许多简单的元类场景:
class Plugin:
"""使用 __init_subclass__ 替代元类"""
_registry = {}
def __init_subclass__(cls, name=None, **kwargs):
super().__init_subclass__(**kwargs)
Plugin._registry[name or cls.__name__] = cls
class MySQLPlugin(Plugin, name="mysql"):
pass
class RedisPlugin(Plugin, name="redis"):
pass
print(Plugin._registry) # {'mysql': <class 'MySQLPlugin'>, 'redis': <class 'RedisPlugin'>}
类装饰器替代元类
def add_repr(cls):
"""类装饰器:自动添加 __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 Point:
def __init__(self, x, y):
self.x = x
self.y = y
print(Point(1, 2)) # Point(x=1, y=2)
元类与描述符配合
class Typed:
"""类型检查描述符"""
def __init__(self, expected_type):
self.expected_type = expected_type
def __set_name__(self, owner, name):
self.name = f"_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.name, None)
def __set__(self, obj, value):
if not isinstance(value, self.expected_type):
raise TypeError(f"期望 {self.expected_type.__name__},得到 {type(value).__name__}")
setattr(obj, self.name, value)
class StrictMeta(type):
"""严格类型检查元类"""
def __new__(mcs, name, bases, namespace):
# 将类型注解转换为 Typed 描述符
annotations = namespace.get('__annotations__', {})
for attr_name, attr_type in annotations.items():
if attr_name not in namespace: # 没有默认值
namespace[attr_name] = Typed(attr_type)
return super().__new__(mcs, name, bases, namespace)
class Person(metaclass=StrictMeta):
name: str
age: int
p = Person()
p.name = "Alice" # OK
# p.name = 42 # TypeError: 期望 str,得到 int