Python - 元类
元类(metaclass)是 “创建类的类”。
在 Python 中,一切皆对象,类本身也是对象;既然类是对象,它必然由某个“东西”创建——这个“东西”就是元类。默认情况下,这个“东西”是内置的 type
元类让你“在类诞生之前”就能插手它的创建过程
⭐面试
type作用:
一、获取对象的类型
二、创建一个新类(用于创建类的元类)
# type第一个参数是类名
# type第二个参数是父类列表
# type第三个参数是类拥有的内容MyClass = type("Person",(list,object),{"a":lambda self:print("a桀"),"__doc__":"这是一个注释!!!"})print(MyClass.__name__,callable(MyClass))
print(MyClass.__doc__)
print(dir(MyClass))
mc = MyClass()
mc.a()
实现单一实例:
class MyType(type):instance1 = {}def __new__(cls, name,bases,dit ):name = "gg"+nameinstance2 = super().__new__(cls , name,bases,dit)return instance2def __call__(cls, *args, **kwargs):if cls not in cls.instance1:cls.instance1[cls] = super().__call__( *args, **kwargs)return cls.instance1class Penson(metaclass=MyType):passp1 =Penson()
p2 =Penson()
print(p1 is p2)
print(Penson.__name__)
进阶
当你需要对所有类或某类家族做统一、不可绕过的改造时,元类是最干净、最强制、最 DRY 的办法。
1. 强制编码规范(公司级代码审计)
需求:所有业务 Model 必须带 created_at / updated_at
,且字段名不能错。
class AuditMeta(type):def __new__(mcs, name, bases, ns):if bases: # 跳过基类自身for f in ('created_at', 'updated_at'):if f not in ns.get('__annotations__', {}):raise TypeError(f'{name} 缺少字段 {f}')return super().__new__(mcs, name, bases, ns)class BaseModel(metaclass=AuditMeta):passclass User(BaseModel):created_at: datetimeupdated_at: datetime
# 少写任何一个字段直接抛 TypeError,CI 阶段就挂掉。
2. 自动生成重复代码(DRY)
需求:每个 ORM 实体类都要实现 to_dict()
class DictableMeta(type):def __new__(mcs, name, bases, ns):def to_dict(self):return {k: getattr(self, k) for k in self.__annotations__}ns['to_dict'] = to_dictreturn super().__new__(mcs, name, bases, ns)class User(metaclass=DictableMeta):id: intname: stru = User(); u.id=1; u.name='tim'
print(u.to_dict()) # {'id': 1, 'name': 'tim'}
3. 单例模式(最保险的实现)
class SingletonMeta(type):_inst = {}def __call__(cls, *a, **kw):if cls not in cls._inst:cls._inst[cls] = super().__call__(*a, **kw)return cls._inst[cls]class Config(metaclass=SingletonMeta):pass
4. 自动注册插件 / 命令 / 路由
需求:把散落在各文件里的 Command
子类自动塞进全局字典,免去手动 import。
registry = {}class CommandMeta(type):def __init__(cls, name, bases, ns):super().__init__(name, bases, ns)if bases: # 过滤基类registry[cls.__name__] = clsclass Command(metaclass=CommandMeta):passclass GitPull(Command): ...
class DockerBuild(Command): ...print(registry) # {'GitPull': <class 'GitPull'>, 'DockerBuild': <class 'DockerBuild'>}
5. 不可变类(冻结属性)
class FrozenMeta(type):def __new__(mcs, name, bases, ns):ns['__slots__'] = tuple(ns.get('__annotations__', {}))ns['__setattr__'] = lambda self, k, v: (_ for _ in ()).throw(AttributeError('frozen'))return super().__new__(mcs, name, bases, ns)class Point(metaclass=FrozenMeta):x: inty: int
6. 枚举值自动校验
class EnumMeta(type):def __new__(mcs, name, bases, ns):choices = {v for k, v in ns.items() if not k.startswith('_')}ns['_choices'] = choicesreturn super().__new__(mcs, name, bases, ns)class Color(metaclass=EnumMeta):RED = 1GREEN = 2BLUE = 3print(Color._choices) # {1, 2, 3}
7. 自动加日志 / 权限检查
def logged(fn):def wrapper(*a, **kw):print(f'[LOG] {fn.__name__}')return fn(*a, **kw)return wrapperclass APIMeta(type):def __new__(mcs, name, bases, ns):for k, v in list(ns.items()):if callable(v) and not k.startswith('_'):ns[k] = logged(v)return super().__new__(mcs, name, bases, ns)class UserAPI(metaclass=APIMeta):def get(self, uid): ...def post(self, data): ...
8. 字段别名 / 数据库映射
class MapperMeta(type):def __new__(mcs, name, bases, ns):ns['_db_map'] = {k: k.replace('_', '').lower() for k in ns.get('__annotations__', {})}return super().__new__(mcs, name, bases, ns)
9. 防止子类忘记调用 super()
class EnsureSuperMeta(type):def __init__(cls, name, bases, ns):if bases and '__init__' in ns:src = ns['__init__'].__code__.co_namesif 'super' not in src:raise TypeError(f'{name}.__init__ 必须调用 super()')super().__init__(name, bases, ns)
10. 框架级 API 设计(Django ORM、SQLAlchemy、Pydantic)
这些库的核心就是元类:
Django Model:把类属性翻译成数据库列。
Pydantic:根据类型注解生成校验器。
Marshmallow:自动生成序列化/反序列化逻辑。
示例(极简 ORM):
class Field:def __init__(self, typ): self.typ = typclass ORMMeta(type):def __new__(mcs, name, bases, ns):ns['_fields'] = {k: v.typ for k, v in ns.items() if isinstance(v, Field)}return super().__new__(mcs, name, bases, ns)class Table(metaclass=ORMMeta):id = Field(int)name = Field(str)print(Table._fields) # {'id': <class 'int'>, 'name': <class 'str'>}
自定义元类
如果你想在 “类创建时” 插手逻辑(比如自动加前缀、注册子类等),可以继承 type
并重写 __new__
或 __init__
:
class Meta(type):def __new__(mcs, name, bases, namespace, **kw):print(f'正在创建类 {name}')# 可以在 namespace 里改、删、增属性namespace['__slots__'] = ('x', 'y') # 强制所有子类带 __slots__return super().__new__(mcs, name, bases, namespace)class Point(metaclass=Meta):pass
什么时候该用元类?
场景 | 解决方案优先级 |
---|---|
只对单个类做装饰 | 类装饰器 |
需要 所有子类 都遵守同一规则 | 元类 |
运行期动态拼装类 | type() 直接构造 |
框架级统一行为(ORM、API、校验) | 元类 |
元类 vs 类装饰器
目的 | 元类 | 类装饰器 |
---|---|---|
拦截并修改类创建过程 | ✅ 在类对象诞生前插手 | ❌ 类对象诞生后包裹 |
对继承链生效(子类也走逻辑) | ✅ 自动 | ❌ 需要手动再装饰子类 |
实现复杂度 | 较高 | 较低 |