__init_subclass__是在子类定义时,类体执行完成后立即被Python自动调用的初始化钩子方法。
下面定义一个Python的脚本去分析类与实例化类的执行顺序
""" 详细演示类定义和实例化的执行顺序 """ from typing import Any print("阶段 1: 定义基类") class Base: def __init_subclass__(cls, **kwargs: Any) -> None: print(f" [Base.__init_subclass__] 被调用!子类: {cls.__name__}") print(f" [Base.__init_subclass__] 此时类体已经执行完成!") print(f" [Base.__init_subclass__] cls.__dict__ = {list(cls.__dict__.keys())}") super().__init_subclass__(**kwargs) def __new__(cls): print(f" [Base.__new__] 被调用!类: {cls.__name__}") print(f" [Base.__new__] 这是在实例化时调用的!") return super().__new__(cls,) def __init__(self,): print(f" [Base.__init__] 被调用!实例: {self.__class__.__name__}") print(f" [Base.__init__] 这是在实例化时调用的!") super().__init__() def Test(self): print("Test ...") print("阶段 2: 定义子类") print("执行: class Child(Base):") class Child(Base): print(" [类体执行] 第 1 行:print 语句") name = "Child" print(" [类体执行] 第 2 行:定义 name 属性") age = 18 print(" [类体执行] 第 3 行:定义 age 属性") print(" [类体执行] 类体执行完成!") # 注意:__init_subclass__ 会在类体执行完成后被调用 print("阶段 3: 实例化") print("执行: child = Child()") child = Child() print(f"\n创建的对象: {child}") print(f"child.name = {child.name}") print(f"child.age = {child.age}") print("总结") print("1. 类体中的代码先执行(从上到下)") print("2. 类体执行完成后,Python 自动调用 __init_subclass__") print("3. 实例化时,先调用 __new__,再调用 __init__") print("4. 实例化时不会调用 __init_subclass__(因为类已经定义好了)")
执行结果:
阶段 1: 定义基类 阶段 2: 定义子类 执行: class Child(Base): [类体执行] 第 1 行:print 语句 [类体执行] 第 2 行:定义 name 属性 [类体执行] 第 3 行:定义 age 属性 [类体执行] 类体执行完成! [Base.__init_subclass__] 被调用!子类: Child [Base.__init_subclass__] 此时类体已经执行完成! [Base.__init_subclass__] cls.__dict__ = ['__module__', 'name', 'age', '__doc__'] 阶段 3: 实例化 执行: child = Child() [Base.__new__] 被调用!类: Child [Base.__new__] 这是在实例化时调用的! [Base.__init__] 被调用!实例: Child [Base.__init__] 这是在实例化时调用的! 创建的对象: <__main__.Child object at 0x00000219B9258560> 阶段 3: 实例化 执行: child = Child() [Base.__new__] 被调用!类: Child [Base.__new__] 这是在实例化时调用的! [Base.__init__] 被调用!实例: Child [Base.__init__] 这是在实例化时调用的! 创建的对象: <__main__.Child object at 0x00000219B9258560> child.name = Child child.age = 18 总结 1. 类体中的代码先执行(从上到下) 2. 类体执行完成后,Python 自动调用 __init_subclass__ 3. 实例化时,先调用 __new__,再调用 __init__ 4. 实例化时不会调用 __init_subclass__(因为类已经定义好了) (dify-api) PS D:\AI\DIFY\dify-main> 执行: child = Child() [Base.__new__] 被调用!类: Child [Base.__new__] 这是在实例化时调用的! [Base.__init__] 被调用!实例: Child [Base.__init__] 这是在实例化时调用的! 创建的对象: <__main__.Child object at 0x00000219B9258560> [Base.__new__] 这是在实例化时调用的! [Base.__init__] 被调用!实例: Child [Base.__init__] 这是在实例化时调用的! 创建的对象: <__main__.Child object at 0x00000219B9258560> [Base.__init__] 被调用!实例: Child [Base.__init__] 这是在实例化时调用的! 创建的对象: <__main__.Child object at 0x00000219B9258560> [Base.__init__] 这是在实例化时调用的! 创建的对象: <__main__.Child object at 0x00000219B9258560> 创建的对象: <__main__.Child object at 0x00000219B9258560> child.name = Child child.age = 18 总结 1. 类体中的代码先执行(从上到下) 2. 类体执行完成后,Python 自动调用 __init_subclass__ 3. 实例化时,先调用 __new__,再调用 __init__ 4. 实例化时不会调用 __init_subclass__(因为类已经定义好了)
执行顺序详解
1. 类定义时的执行顺序
============================================================ 完整执行流程总结 ============================================================ 阶段 1: 定义 Base 类时 ↓ 1. 执行 Base 类体(属性定义、方法定义等) ↓ 2. 定义 __init_subclass__ 方法(只是定义,不执行) ↓ 3. Base 类定义完成 阶段 2: 定义 Child 类时 ↓ 1. 执行 Child 类体(按顺序执行所有代码) ↓ 2. Child 类体执行完成 ↓ 3. Python 自动调用 Base.__init_subclass__(Child) ↓ 4. Child 类定义完成 阶段 3: 实例化 Child 时 ↓ 1. 调用 Child.__new__(创建对象) ↓ 2. 调用 Child.__init__(初始化对象) 注意:实例化时不会调用 __init_subclass__,因为类已经定义好了
2. 为什么类体先执行,然后才调用父类 __init_subclass__?
这是 Python 的设计:
- 类体先执行:Python 需要先执行类体,收集所有类属性
- 然后调用 __init_subclass__:此时类属性已经收集完成,父类可以访问这些属性
查看输出:
[类体执行] 第 1 行:print 语句 [类体执行] 第 2 行:定义 name 属性 [类体执行] 第 3 行:定义 age 属性 [类体执行] 类体执行完成! [Base.__init_subclass__] 被调用!子类: Child [Base.__init_subclass__] 此时类体已经执行完成! [Base.__init_subclass__] cls.__dict__ = ['__module__', 'name', 'age', '__doc__']
可以看到,__init_subclass__ 被调用时,name 和 age 已经在 cls.__dict__ 中了。
3. 为什么实例化时不调用 __init_subclass__?
因为 __init_subclass__ 只在类定义时调用一次,不是每次实例化都调用。
实例化时的调用链: ↓ 1. Child() 被调用 ↓ 2. Python 调用 Child.__new__(Child) ← 创建对象 ↓ 3. Python 调用 Child.__init__(self) ← 初始化对象 ↓ 4. 对象创建完成
__init_subclass__ 不会在这里调用,因为:
- 类已经定义好了(在导入时)
- __init_subclass__ 只在类定义时调用一次
- 实例化时只需要创建对象,不需要重新定义类
4. 关键点总结
| 问题 | 答案 |
| 为什么类体先执行? | Python 需要先收集所有类属性,然后才能让 __init_subclass__ 访问它们 |
| 为什么 __init_subclass__ 在类体之后? | 这样父类可以访问子类的所有属性(如 __tablename__、mapped_column 等) |
| 为什么实例化时不调用 __init_subclass__? | __init_subclass__ 只在类定义时调用一次,实例化时类已经定义好了 |
| 实例化时调用什么? | __new__(创建对象)和 __init__(初始化对象) |
5. 为什么这样设计?
- 类体先执行:让所有类属性先定义好
- 然后调用 __init_subclass__:父类可以访问子类的所有属性
- 实例化时只调用 __new__ 和 __init__:类已经定义好,只需要创建对象
这就是为什么在 SQLAlchemy 中,DeclarativeBase.__init_subclass__ 可以访问 Account.__tablename__ 和所有 mapped_column 定义的原因。这个执行顺序是 Python 的设计,不是 SQLAlchemy 的特殊处理。
6.常见特殊方法的执行时机
__init_subclass__ # 定义子类时自动调用
__new__ # 创建对象时自动调用
__init__ # 初始化对象时自动调用
__str__ # 调用 str(obj) 时自动调用
__repr__ # 调用 repr(obj) 时自动调用
__getattr__ # 访问不存在的属性时自动调用
# ... 还有很多