完整讲解 · 5 段教学·配 3 道练习题·预计 30 分钟
本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。
先送一句 Tim Peters(Python 界的资深玩家)说过的话:
元类是深度的魔法,99% 的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。
听起来好像在劝退。但既然元类是 Python 类型系统的根,了解一下它可以让你真正看懂 class 关键字背后发生了什么。
在大多数语言里,类是「描述如何生成对象的代码段」。Python 也是这样——但 Python 多了一层:
在 Python 中,类本身也是一个对象。
这听起来有点绕。下面这段最普通的代码:
class ObjectCreator:
pass
obj = ObjectCreator()
print(obj)
print(ObjectCreator)输出(地址会不同):
<__main__.ObjectCreator object at 0x...>
<class '__main__.ObjectCreator'>
注意第二行——ObjectCreator 这个类,本身也能被打印。也就是说它是个对象。既然是对象,我们就能:
class ObjectCreator:
pass
# 把类当参数传给函数
def echo(x):
print(x)
echo(ObjectCreator)
# 把类赋值给变量
Alias = ObjectCreator
print(Alias)输出:
<class '__main__.ObjectCreator'>
<class '__main__.ObjectCreator'>
类可以传参、可以赋值、可以打印——和函数一样,是「一等公民」。
type() 不止是「查类型」各位都用过 type() 函数——查一个变量是什么类型:
print(type(123))
print(type('abc'))输出:
<class 'int'>
<class 'str'>
这是 type() 的第一种用法:传一个对象,返回它的类型。
那如果我把一个类传给 type() 呢?
class Hello:
def hi(self):
print('Hello')
h = Hello()
print(type(h)) # 实例的类型
print(type(Hello)) # 类的类型输出:
<class '__main__.Hello'>
<class 'type'>
注意第二行——Hello 这个类的类型是 type。
换句话说:Hello 是 type 的一个实例。
Python 里所有东西都是对象,都有 __class__ 属性:
print((123).__class__)
print('abc'.__class__)
def fn(): pass
print(fn.__class__)
class Eat: pass
print(Eat().__class__)输出:
<class 'int'>
<class 'str'>
<class 'function'>
<class '__main__.Eat'>
这些 class 自己又是从哪儿来的?继续往上一层:
print((123).__class__.__class__)
print('abc'.__class__.__class__)
print(Eat().__class__.__class__)输出:
<class 'type'>
<class 'type'>
<class 'type'>
全是 type。这就是核心结论:
type是 Python 内建的「元类」——所有类的类。
| 对象 | 它的类 |
|---|---|
123 | int |
'abc' | str |
int / str / 你自己写的类 | type |
「类」也是「对象」——是 type 的实例。这就是元类的第一层意思。
type() 动态创建类既然 type 是创建类的元类,那我们能不能直接调用 type 来造一个类?答案是能。
type() 还有第二种用法——传三个参数:
type(类名, 父类元组, 属性字典)| 参数 | 说明 |
|---|---|
| 类名 | 字符串,例如 'Hello' |
| 父类元组 | 单父类要写成单元素元组:(object,),多继承就多个 |
| 属性字典 | {'方法名': 函数, '属性名': 值} |
普通写法:
class Hello:
def hello(self, name='Py'):
print('Hello,', name)用 type() 三参数完全等价的写法:
def hello_method(self, name='Py'):
print('Hello,', name)
Hello = type('Hello', (object,), {'hello': hello_method})
h = Hello()
h.hello()
print(type(Hello))输出:
Hello, Py
<class 'type'>
这两种写法完全等价——Python 解释器看到 class 关键字时,背后做的事其实就是「调一下 type()」。
动态语言的核心特性:类不是编译时定下来的,而是运行时创建的。
这意味着你可以:
静态语言(Java、C++)想做这件事得先生成源码再编译,麻烦得多。Python 一行 type() 就搞定。
注意属性字典里值可以是任何东西——函数变成方法,普通值变成类属性:
def greet(self):
print(f'你好,我是 {self.name}')
Person = type('Person', (object,), {
'name': '匿名',
'greet': greet,
})
p = Person()
p.greet()输出:
你好,我是 匿名
下一题就来动手用 type() 造一个类。
到这里我们已经知道:
typetype() 三参数能动态造类那「自定义元类」就是顺理成章的下一步——继承 type 写一个自己的元类,让它在创建类的时候做一些定制操作。
记住这条因果链:
先有 metaclass,再用它创建 class,最后用 class 创建实例。
普通类是 type 创建的;自定义元类替代 type,在「类被创建的那一刻」插一脚——可以修改类的属性、方法、注册到表里、检查命名规范等等。
type,重写 __new__class AnimalMeta(type):
def __new__(mcs, name, bases, attrs):
# mcs = 元类自身(AnimalMeta)
# name = 正在创建的类的名字(字符串)
# bases = 父类元组
# attrs = 类体里定义的属性字典
# 给所有用此元类的类自动加一个 species 属性
attrs['species'] = 'animal'
# 复用父类(type)的 __new__ 真正创建类
return super().__new__(mcs, name, bases, attrs)class X(metaclass=...)Python 3 里指定元类的语法是在 class 头部加 metaclass= 关键字参数:
class Dog(metaclass=AnimalMeta):
pass
class Cat(metaclass=AnimalMeta):
pass
print(Dog.species)
print(Cat.species)输出:
animal
animal
注意——Dog 类体里我们一行 species 都没写,是 AnimalMeta.__new__ 在创建类时偷偷塞进去的。这就是元类的能力。
__new__ 的四个参数| 参数 | 含义 |
|---|---|
mcs | 元类自己(类似实例方法里的 self) |
name | 正在创建的类的名字(字符串) |
bases | 父类元组 |
attrs | 类体里所有名字 → 值的字典(包括方法) |
所以元类做的就三件事:
attrs(加属性、改方法、做检查)概念上很简单——元类就是「负责造类的工厂」。
回到开头 Tim Peters 那句话:
元类是深度的魔法,99% 的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。
这句话不是在劝退,而是在给一个判断标准:
dataclass、__init_subclass__ 这些工具够用了。最经典的例子是 Django ORM:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()用起来:
guy = Person(name='bob', age=35)
print(guy.age) # 直接是 int 35,不是 IntegerField 对象注意——age 在类体里写的是 IntegerField(),但实例化后访问 guy.age 拿到的却是普通的 int。这中间发生了什么?
models.Model 用元类拦截了类的创建过程:
Field 对象,记录哪些是字段__init__:让用户传参时自动赋值__getattr__,从内部存储里取真值整套魔法是为了把「定义一个类」变成「定义一张表」——用户写得简洁,框架在背后负重前行。
类似的场景:Pydantic、SQLAlchemy、FastAPI 的请求模型、各种 schema 库——都重度依赖元类(或者它的轻量替代 __init_subclass__)。
总结一下你刚学到的:
| 能力 | 怎么做 |
|---|---|
| 拦截类的创建 | 自定义 metaclass 重写 __new__ |
| 给所有子类加属性 | 在 __new__ 里改 attrs 字典 |
| 检查类的命名规范 | 在 __new__ 里 if not name.startswith(...): raise |
| 注册类到全局表 | 在 __new__ 里 REGISTRY[name] = cls |
| 动态生成类 | type(name, bases, attrs) 三参数 |
最后一句忠告:能用更简单的办法解决就别动元类。
__init__ → dataclass__init_subclass__(Python 3.6+,比元类轻量)元类是「核武器」——威力大,副作用也大(继承冲突、调试困难、IDE 提示失效)。看懂它能让你读懂别人的框架代码;亲自动手写元类的机会,可能整个职业生涯也没几次。
到这里,元类这一篇就讲完了。各位别被「魔法」二字吓到——它的本质就是「类也是对象,所以创建类也可以被自定义」,仅此而已。
读完了?动手练一遍才算真会。
去闯关模式练习 →