一、从一个问题出发
当你定义一个基类,希望所有子类在被定义时(而非实例化时)就自动完成某些注册、校验或增强逻辑,你会怎么做?
传统方案是元类(metaclass),但元类的心智负担极重。python 3.6 引入的 __init_subclass__ 正是为了解决这一痛点——用一个普通的类方法,优雅地拦截子类的创建过程。
二、它是什么
class base:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# cls 是正在被定义的子类,不是 base 本身
__init_subclass__ 是一个隐式的 classmethod,定义在父类中,每当有子类继承该父类时,python 解释器会自动调用它,并将新创建的子类作为第一个参数 cls 传入。
pep 487(python 3.6)正式引入此机制,目标是提供一种比元类更轻量的子类定制钩子。
三、调用时机与调用链
class a:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(f"a.__init_subclass__ called: {cls}")
class b(a): # 触发 a.__init_subclass__(b)
pass
class c(b): # 触发 a.__init_subclass__(c)(沿 mro 向上找)
pass
输出:
a.__init_subclass__ called: <class '__main__.b'>
a.__init_subclass__ called: <class '__main__.c'>
关键细节:
| 时机 | 是否触发 |
|---|---|
| 定义 base 本身 | ❌ 不触发 |
| 直接继承 base 的子类被定义 | ✅ 触发 |
| 子类的子类被定义 | ✅ 触发(沿 mro 传播) |
| 实例化子类 | ❌ 不触发 |
四、传递关键字参数
__init_subclass__ 最精妙的设计之一是支持通过 class 语句的关键字参数向父类传递配置:
class animal:
def __init_subclass__(cls, sound: str = "...", **kwargs):
super().__init_subclass__(**kwargs)
cls.sound = sound
print(f"registered {cls.__name__} with sound '{sound}'")
class dog(animal, sound="woof"):
pass
class cat(animal, sound="meow"):
pass
print(dog.sound) # woof
print(cat.sound) # meow这些关键字参数不会出现在 __init__ 中,它们专属于类定义阶段,语义清晰,无副作用。
⚠️ 务必用 **kwargs 接收未消费的参数并传给 super(),否则多重继承时会因参数不匹配而抛出 typeerror。
五、核心应用场景
5.1 自动注册子类(插件系统)
这是最经典的用法,无需手动维护注册表:
class handler:
_registry: dict[str, type] = {}
def __init_subclass__(cls, name: str | none = none, **kwargs):
super().__init_subclass__(**kwargs)
key = name or cls.__name__.lower()
handler._registry[key] = cls
print(f"handler '{key}' registered.")
class jsonhandler(handler, name="json"):
def handle(self): ...
class xmlhandler(handler, name="xml"):
def handle(self): ...
# 无需任何手动注册
print(handler._registry)
# {'json': <class 'jsonhandler'>, 'xml': <class 'xmlhandler'>}
工厂方法只需查表:
@classmethod
def create(cls, name: str) -> "handler":
return cls._registry[name]()
5.2 强制接口约束(抽象检查的增强版)
abc.abc 在实例化时才报错,__init_subclass__ 可以在类定义时就报错:
class strictbase:
_required_methods = ("execute", "rollback")
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for method in strictbase._required_methods:
if not callable(getattr(cls, method, none)):
raise typeerror(
f"{cls.__name__} must implement '{method}'"
)
class goodtransaction(strictbase):
def execute(self): ...
def rollback(self): ...
class badtransaction(strictbase): # 立即 typeerror!
def execute(self): ...
# 忘记实现 rollback
5.3 自动注入行为(装饰器的类级等价物)
import functools, time
class timed:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for name, fn in vars(cls).items():
if callable(fn) and not name.startswith("_"):
setattr(cls, name, _timer(fn))
def _timer(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
t = time.perf_counter()
result = fn(*args, **kwargs)
print(f"{fn.__name__}: {time.perf_counter() - t:.4f}s")
return result
return wrapper
class myservice(timed):
def fetch(self): time.sleep(0.1)
def process(self): time.sleep(0.2)
svc = myservice()
svc.fetch() # fetch: 0.1002s
svc.process() # process: 0.2001s
5.4 orm 字段收集(django/sqlalchemy 同款思路)
class field:
def __init__(self, col_type):
self.col_type = col_type
class modelmeta:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls._fields = {
k: v for k, v in vars(cls).items()
if isinstance(v, field)
}
print(f"model '{cls.__name__}' fields: {list(cls._fields)}")
class user(modelmeta):
id = field("integer")
name = field("varchar")
email = field("varchar")
# 输出: model 'user' fields: ['id', 'name', 'email']
六、与元类的对比
| 维度 | __init_subclass__ | 元类(metaclass) |
|---|---|---|
| 语法复杂度 | 低,普通方法 | 高,需理解 type 体系 |
| 作用时机 | 子类定义完成后 | 子类定义过程中(可修改类命名空间) |
| 能否修改类命名空间 | ❌ | ✅ |
| 多重继承兼容性 | 好(用 super() 链式调用) | 差(元类冲突是常见陷阱) |
| 传递配置 | 关键字参数,优雅 | __new__ 参数,繁琐 |
| 适用场景 | 注册、校验、增强 | 需要深度控制类创建过程 |
结论: 能用 __init_subclass__ 解决的问题,不必引入元类。
七、与__set_name__的协同
python 3.6 同期引入的 __set_name__ 在描述符被赋值给类属性时触发,两者常配合使用:
class validatedfield:
def __set_name__(self, owner, name):
# 此时 owner 是拥有该描述符的类,name 是属性名
self.name = name
def __set__(self, obj, value):
if not isinstance(value, int):
raise typeerror(f"{self.name} must be int")
obj.__dict__[self.name] = value
class schema:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 可在此对 cls 上的 validatedfield 做额外处理
fields = [k for k, v in vars(cls).items()
if isinstance(v, validatedfield)]
cls._validated_fields = fields
class config(schema):
timeout = validatedfield()
retries = validatedfield()
八、多重继承下的正确姿势
多重继承时,__init_subclass__ 沿 mro 链式调用,必须调用 super(),否则链条断裂:
class loggable:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs) # ✅ 必须
cls._log = true
class serializable:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs) # ✅ 必须
cls._serializable = true
class document(loggable, serializable):
pass
# document._log 和 document._serializable 均已设置
若省略 super().__init_subclass__(**kwargs),mro 中后续的 __init_subclass__ 将被静默跳过,引发难以追踪的 bug。
九、常见陷阱总结
1. 忘记调用 super()
链式调用断裂,多重继承场景必现问题。
2. 关键字参数未用 **kwargs 透传
# 错误示范
def __init_subclass__(cls, my_param=none): # 漏掉 **kwargs
super().__init_subclass__() # 漏掉 **kwargs
一旦有其他父类也消费关键字参数,即报 typeerror。
3. 误以为 cls 是父类
cls 始终是正在被创建的那个子类,不是定义了 __init_subclass__ 的类。
4. 在 __init_subclass__ 中访问未完全初始化的子类
某些装饰器逻辑若依赖子类的 __init__ 已存在,要注意此时子类的方法已在 vars(cls) 中,但父类方法通过 mro 继承,不在 vars(cls) 里。
十、一句话总结
__init_subclass__ 是 python 3.6 给类体系提供的轻量级生命周期钩子,它在子类被定义的瞬间触发,让父类得以观察、校验、增强乃至注册每一个子类——用最小的复杂度,实现了元类 80% 的日常用途。
掌握它,你将拥有一把构建插件系统、orm、接口约束框架的利器,同时保持代码的可读性与可维护性。
到此这篇关于深入理解python __init_subclass__的使用的文章就介绍到这了,更多相关python __init_subclass__内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论