Python 元类是什么?type 和 metaclass 有什么关系?
简化版
元类是创建类的类。普通对象由类创建,而类对象通常由 type 创建;通过自定义 metaclass,可以干预类创建过程,例如注册子类、校验类定义、给类自动添加属性或方法。
详细版
在 Python 中:
class User:
pass
u = User()
u 是 User 的实例,而 User 本身也是对象:
print(type(u)) # <class '__main__.User'>
print(type(User)) # <class 'type'>
这说明普通类对象通常是由 type 创建的。
自定义元类:
class ModelMeta(type):
def __new__(mcls, name, bases, namespace):
namespace["table_name"] = name.lower()
return super().__new__(mcls, name, bases, namespace)
class User(metaclass=ModelMeta):
pass
print(User.table_name) # user
元类适合框架底层,不适合普通业务里随意使用。很多需求可以用类装饰器、__init_subclass__ 或普通继承解决。
完整版教学
一、先理解类也是对象
Python 里几乎一切都是对象,类也不例外。
class User:
pass
执行类定义时,Python 会创建一个类对象 User。
可以把类赋值给变量:
Alias = User
u = Alias()
也可以查看类的类型:
print(type(User)) # type
既然类是对象,就会有“谁创建了类对象”这个问题。答案通常是 type。
二、type 的两个角色
type 常见用法是查看对象类型:
type(123)
type("abc")
但 type 也可以动态创建类:
User = type("User", (), {"role": "guest"})
这大致等价于:
class User:
role = "guest"
三个参数含义:
- 类名;
- 父类元组;
- 类命名空间字典。
这说明 class 语句背后也会经历类对象创建过程。
三、元类如何介入类创建
自定义元类通常继承 type:
class Meta(type):
def __new__(mcls, name, bases, namespace):
print("creating", name)
return super().__new__(mcls, name, bases, namespace)
使用:
class User(metaclass=Meta):
pass
当 Python 创建 User 类对象时,会调用 Meta.__new__。
你可以在这里检查类定义、修改命名空间、注册类信息。
四、元类适合做什么
常见用途:
- ORM 收集字段定义;
- 插件系统自动注册子类;
- 校验类必须定义某些属性;
- 自动生成类方法;
- 框架级声明式 API。
例如简单注册:
registry = {}
class RegistryMeta(type):
def __new__(mcls, name, bases, namespace):
cls = super().__new__(mcls, name, bases, namespace)
if name != "Base":
registry[name] = cls
return cls
class Base(metaclass=RegistryMeta):
pass
class User(Base):
pass
创建 User 类时,它会自动进入 registry。
五、元类为什么要谨慎
元类强大,但也增加理解成本。
问题包括:
- 类创建逻辑隐藏,不容易追踪;
- 多个元类难以组合;
- 调试门槛高;
- 普通业务开发者不一定熟悉;
- 很多场景有更简单替代方案。
如果只是想在子类创建时做一点注册,可以考虑 __init_subclass__:
class Base:
registry = []
def __init_subclass__(cls):
super().__init_subclass__()
Base.registry.append(cls)
如果只是想改一个类,可以用类装饰器。
六、元类和类装饰器的区别
类装饰器在类创建完成后处理类:
def add_table_name(cls):
cls.table_name = cls.__name__.lower()
return cls
@add_table_name
class User:
pass
元类是在类创建过程中介入。它能控制得更早、更底层。
选择建议:
- 简单修改类:类装饰器;
- 子类创建钩子:
__init_subclass__; - 框架级类创建控制:元类。
七、常见误区与追问
记忆钩子:实例由类创建,类由元类创建。普通业务代码大多改“实例行为”,框架底层才常改“类是怎么被创建出来的”。
- 误区:元类就是父类。 父类参与实例方法和属性继承,元类负责创建类对象;
class User(Base)里的Base是父类,metaclass=Meta才是指定元类。 - 追问:
type为什么既能看类型又能造类?type(obj)返回对象的类型;type(name, bases, namespace)可以动态创建类,这体现了类本身也是对象。 - 误区:元类适合解决普通业务复用。 元类会影响类创建流程,理解成本高;普通复用优先考虑函数、组合、类装饰器或
__init_subclass__。 - 追问:ORM 为什么常用元类? ORM 需要在类定义阶段收集字段、生成映射、注册模型元信息;这些都发生在类对象创建时,元类可以统一拦截。
- 误区:类装饰器和元类完全等价。 类装饰器处理已经创建好的类,元类能参与类创建前后的更底层流程,例如准备命名空间、校验基类、控制类对象生成。
- 追问:元类冲突是什么? 多个父类如果来自不兼容的元类,Python 无法确定子类该由哪个元类创建,就可能出现 metaclass conflict,需要设计共同元类或调整继承结构。
八、加强记忆
对象由类创建,类通常由 type 创建,元类就是创建类的类。metaclass 能干预类定义变成类对象的过程,很适合框架底层,但业务代码里要谨慎。能用类装饰器或 __init_subclass__ 解决,就别急着上元类。