小白学 Python
AI 编程指南GitHub
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录关于本站联系方式隐私政策GitHub

元类

完整讲解 · 5 段教学·配 3 道练习题·预计 30 分钟

本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。

教学 01 / 05

元类:类也是对象

先送一句 Tim Peters(Python 界的资深玩家)说过的话:

元类是深度的魔法,99% 的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。

听起来好像在劝退。但既然元类是 Python 类型系统的根,了解一下它可以让你真正看懂 class 关键字背后发生了什么。

类是什么?

在大多数语言里,类是「描述如何生成对象的代码段」。Python 也是这样——但 Python 多了一层:

在 Python 中,类本身也是一个对象。

这听起来有点绕。下面这段最普通的代码:

python到闯关页运行这段 →
class ObjectCreator:
    pass

obj = ObjectCreator()
print(obj)
print(ObjectCreator)

输出(地址会不同):

<__main__.ObjectCreator object at 0x...>
<class '__main__.ObjectCreator'>

注意第二行——ObjectCreator 这个类,本身也能被打印。也就是说它是个对象。既然是对象,我们就能:

python到闯关页运行这段 →
class ObjectCreator:
    pass

# 把类当参数传给函数
def echo(x):
    print(x)

echo(ObjectCreator)

# 把类赋值给变量
Alias = ObjectCreator
print(Alias)

输出:

<class '__main__.ObjectCreator'>
<class '__main__.ObjectCreator'>

类可以传参、可以赋值、可以打印——和函数一样,是「一等公民」。

这一节我们要回答的问题

  • 一个类的「类」是什么?
  • 怎么在运行时动态创建一个类?
  • 什么是元类(metaclass),怎么自定义一个?
  • 实际工作里我什么时候会用到?(剧透:大概率用不到,但搞懂它能看懂 ORM、Pydantic 这类框架的魔法)
教学 02 / 05

一、type() 不止是「查类型」

各位都用过 type() 函数——查一个变量是什么类型:

python到闯关页运行这段 →
print(type(123))
print(type('abc'))

输出:

<class 'int'>
<class 'str'>

这是 type() 的第一种用法:传一个对象,返回它的类型。

类的类型是什么?

那如果我把一个类传给 type() 呢?

python到闯关页运行这段 →
class Hello:
    def hi(self):
        print('Hello')

h = Hello()
print(type(h))        # 实例的类型
print(type(Hello))    # 类的类型

输出:

<class '__main__.Hello'>
<class 'type'>

注意第二行——Hello 这个类的类型是 type。

换句话说:Hello 是 type 的一个实例。

一切对象都源自类,一切类都源自 type

Python 里所有东西都是对象,都有 __class__ 属性:

python到闯关页运行这段 →
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 自己又是从哪儿来的?继续往上一层:

python到闯关页运行这段 →
print((123).__class__.__class__)
print('abc'.__class__.__class__)
print(Eat().__class__.__class__)

输出:

<class 'type'>
<class 'type'>
<class 'type'>

全是 type。这就是核心结论:

type 是 Python 内建的「元类」——所有类的类。

对象它的类
123int
'abc'str
int / str / 你自己写的类type

「类」也是「对象」——是 type 的实例。这就是元类的第一层意思。

练习 1 / 3类的类是什么?去闯关页做这题 →
教学 03 / 05

二、用 type() 动态创建类

既然 type 是创建类的元类,那我们能不能直接调用 type 来造一个类?答案是能。

type() 还有第二种用法——传三个参数:

python到闯关页运行这段 →
type(类名, 父类元组, 属性字典)
参数说明
类名字符串,例如 'Hello'
父类元组单父类要写成单元素元组:(object,),多继承就多个
属性字典{'方法名': 函数, '属性名': 值}

一行造一个类

普通写法:

python到闯关页运行这段 →
class Hello:
    def hello(self, name='Py'):
        print('Hello,', name)

用 type() 三参数完全等价的写法:

python到闯关页运行这段 →
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()」。

这有什么用

动态语言的核心特性:类不是编译时定下来的,而是运行时创建的。

这意味着你可以:

  • 根据配置文件、数据库 schema 在程序跑起来后才生成类
  • 在测试里临时拼一个 mock 类
  • ORM 框架(Django Models、SQLAlchemy)就是用这个机制把表结构转成类

静态语言(Java、C++)想做这件事得先生成源码再编译,麻烦得多。Python 一行 type() 就搞定。

小练手

注意属性字典里值可以是任何东西——函数变成方法,普通值变成类属性:

python到闯关页运行这段 →
def greet(self):
    print(f'你好,我是 {self.name}')

Person = type('Person', (object,), {
    'name': '匿名',
    'greet': greet,
})

p = Person()
p.greet()

输出:

你好,我是 匿名

下一题就来动手用 type() 造一个类。

练习 2 / 3用 type() 三参数动态造类去闯关页做这题 →
教学 04 / 05

三、自定义元类

到这里我们已经知道:

  • 类是对象
  • 类的类是 type
  • type() 三参数能动态造类

那「自定义元类」就是顺理成章的下一步——继承 type 写一个自己的元类,让它在创建类的时候做一些定制操作。

元类的工作时机

记住这条因果链:

先有 metaclass,再用它创建 class,最后用 class 创建实例。

普通类是 type 创建的;自定义元类替代 type,在「类被创建的那一刻」插一脚——可以修改类的属性、方法、注册到表里、检查命名规范等等。

写法:继承 type,重写 __new__

python到闯关页运行这段 →
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= 关键字参数:

python到闯关页运行这段 →
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类体里所有名字 → 值的字典(包括方法)

所以元类做的就三件事:

  1. 拦截类的创建
  2. 修改 attrs(加属性、改方法、做检查)
  3. 返回修改后的类

概念上很简单——元类就是「负责造类的工厂」。

练习 3 / 3自定义元类自动加属性去闯关页做这题 →
教学 05 / 05

四、什么时候真的需要元类

回到开头 Tim Peters 那句话:

元类是深度的魔法,99% 的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。

这句话不是在劝退,而是在给一个判断标准:

  • 如果你在写业务代码——99% 的概率用不到元类。继承、装饰器、dataclass、__init_subclass__ 这些工具够用了。
  • 如果你在写框架——尤其是「让用户定义类的方式去声明数据结构」的那种——元类才是合适的工具。

元类典型场景:声明式 API

最经典的例子是 Django ORM:

python到闯关页运行这段 →
class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

用起来:

python到闯关页运行这段 →
guy = Person(name='bob', age=35)
print(guy.age)   # 直接是 int 35,不是 IntegerField 对象

注意——age 在类体里写的是 IntegerField(),但实例化后访问 guy.age 拿到的却是普通的 int。这中间发生了什么?

models.Model 用元类拦截了类的创建过程:

  1. 扫描类体里所有 Field 对象,记录哪些是字段
  2. 重写 __init__:让用户传参时自动赋值
  3. 让属性访问走描述符或 __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) 三参数

不到万不得已不要用元类

最后一句忠告:能用更简单的办法解决就别动元类。

  • 想给类加方法 → 装饰器 / mixin
  • 想要自动生成 __init__ → dataclass
  • 想在子类创建时做点事 → __init_subclass__(Python 3.6+,比元类轻量)

元类是「核武器」——威力大,副作用也大(继承冲突、调试困难、IDE 提示失效)。看懂它能让你读懂别人的框架代码;亲自动手写元类的机会,可能整个职业生涯也没几次。

到这里,元类这一篇就讲完了。各位别被「魔法」二字吓到——它的本质就是「类也是对象,所以创建类也可以被自定义」,仅此而已。

读完了?动手练一遍才算真会。

去闯关模式练习 →