Python基础 - 类中__init_subclass_方法执行顺序

简介: 本文详解Python类与实例化时的执行顺序:类定义时先执行类体,再调用`__init_subclass__`;实例化时先调用`__new__`创建对象,再调用`__init__`初始化。`__init_subclass__`仅在类定义时执行一次,确保父类可访问子类属性,如SQLAlchemy中表结构的构建原理。

__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 的设计:

  1. 类体先执行:Python 需要先执行类体,收集所有类属性
  2. 然后调用 __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__ 被调用时,nameage 已经在 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. 为什么这样设计

  1. 类体先执行:让所有类属性先定义好
  2. 然后调用 __init_subclass__:父类可以访问子类的所有属性
  3. 实例化时只调用 __new____init__:类已经定义好,只需要创建对象

这就是为什么在 SQLAlchemy 中,DeclarativeBase.__init_subclass__ 可以访问 Account.__tablename__ 和所有 mapped_column 定义的原因。这个执行顺序是 Python 的设计,不是 SQLAlchemy 的特殊处理。


6.常见特殊方法的执行时机

__init_subclass__  # 定义子类时自动调用

__new__            # 创建对象时自动调用

__init__           # 初始化对象时自动调用

__str__            # 调用 str(obj) 时自动调用

__repr__           # 调用 repr(obj) 时自动调用

__getattr__        # 访问不存在的属性时自动调用

# ... 还有很多

目录
相关文章
|
29天前
|
存储 自然语言处理 数据库
RAG(检索增强生成)技术简介
RAG(检索增强生成)通过结合信息检索与文本生成,提升大模型回答的准确性与时效性。它利用向量数据库实现语义检索,支持智能客服、医疗诊断、法律咨询等场景,解决知识更新难、专业性不足等问题,并以“按需取用”机制突破上下文限制,兼顾效率与隐私。
333 4
|
26天前
|
运维 Kubernetes Go
别再靠人肉运维了:Kubernetes Operator 才是运维自动化的终极形态
别再靠人肉运维了:Kubernetes Operator 才是运维自动化的终极形态
92 6
|
28天前
|
存储 安全 对象存储
手把手教你如何开启阿里云OSS存储服务并布署到网站1
本文介绍如何在阿里云OSS创建首个存储空间(Bucket)并配置RAM用户。首先登录OSS控制台创建Bucket,填写名称与地域;随后进入RAM控制台创建用户并获取访问密钥,实现安全访问。附详细步骤图示与链接示例,助您快速搭建对象存储环境。
318 6
|
11天前
|
存储 运维 Kubernetes
K8s 持久化存储怎么选?别只盯着性能,能不能活下来更重要
K8s 持久化存储怎么选?别只盯着性能,能不能活下来更重要
88 6
|
13天前
|
关系型数据库 项目管理 数据安全/隐私保护
Leantime:开源项目管理神器
Leantime是一款专为非专业项目经理设计的开源项目管理工具,在Jira的臃肿和Trello的简化之间找到了完美平衡。它集成了战略规划、敏捷看板、甘特图、知识管理、工时跟踪等全面功能,支持Docker一键部署。无论是创业团队还是企业部门,Leantime都能以极低的学习成本,让每位成员轻松参与项目协作。告别过度复杂的工具,用这款轻量而强大的神器,为你的2026年项目计划保驾护航。
115 16
 Leantime:开源项目管理神器
|
16天前
|
Kubernetes 安全 API
Kubernetes API 扩展与安全:别让谁都能对集群“下手”
Kubernetes API 扩展与安全:别让谁都能对集群“下手”
108 15
|
1月前
|
Kubernetes 应用服务中间件 API
应对 Nginx Ingress 退役,是时候理清这些易混淆的概念了
本文希望提供一种更简单的方式,来理解这些容易混淆的技术概念:Nginx、Ingress、Ingress Controller、Ingress API、Nginx Ingress、Higress、Gateway API。
787 71
|
1月前
|
人工智能 测试技术 开发者
AI Coding后端开发实战:解锁AI辅助编程新范式
本文系统阐述了AI时代开发者如何高效协作AI Coding工具,强调破除认知误区、构建个人上下文管理体系,并精准判断AI输出质量。通过实战流程与案例,助力开发者实现从编码到架构思维的跃迁,成为人机协同的“超级开发者”。
1616 106
|
19天前
|
存储 运维 Kubernetes
K8s 集群不是不需要备份,只是你还没被教育过:Velero / Kasten 在大规模集群里的真实落地
K8s 集群不是不需要备份,只是你还没被教育过:Velero / Kasten 在大规模集群里的真实落地
137 10
|
25天前
|
网络协议 Dubbo Java
从 TCP 到 RPC:彻底搞懂「HTTP 与 RPC用法区别」
本文深入剖析HTTP与RPC的本质区别,从TCP底层原理讲起,解析粘包拆包、协议封装等核心问题,梳理二者演进脉络。通过对比服务发现、传输性能、适用场景等维度,结合Dubbo、gRPC等框架,帮你按场景精准选型,彻底搞懂微服务通信的技术逻辑。
182 5