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__        # 访问不存在的属性时自动调用

# ... 还有很多

目录
相关文章
|
7月前
|
存储 关系型数据库 数据库
Flask中定义类使用Sqlalchemy时,mapped_column详细讲解
本文详解 SQLAlchemy 2.0 中 `mapped_column` 的用法,介绍其语法结构、常用参数(如 `nullable`、`default`、`server_default`、`onupdate` 等),并通过 DIFY 的 Account 模型实例解析字段定义逻辑,对比数据库列与运行时字段的区别,帮助理解 ORM 映射机制。
662 3
|
定位技术 数据安全/隐私保护
MIKE 21 教程 1.3 网格搭建界面介绍之网格生成 (Mesh Generator 工具)
MIKE 21 教程 1.3 网格搭建界面介绍之网格生成 (Mesh Generator 工具)
|
7月前
|
IDE 数据库 开发工具
SQLALCHEMY中DeclarativeBase,MappedAsDataclass
Python 中 SQLAlchemy 中这一开源库的使用方法
317 3
|
Java Linux 开发工具
Linux下解压修改jar包并运行
Linux下解压修改jar包并运行
2296 0
|
7月前
|
存储 自然语言处理 数据库
RAG(检索增强生成)技术简介
RAG(检索增强生成)通过结合信息检索与文本生成,提升大模型回答的准确性与时效性。它利用向量数据库实现语义检索,支持智能客服、医疗诊断、法律咨询等场景,解决知识更新难、专业性不足等问题,并以“按需取用”机制突破上下文限制,兼顾效率与隐私。
1091 4
|
7月前
|
数据采集 监控 数据可视化
快速上手:LangChain + AgentRun 浏览器沙箱极简集成指南
AgentRun Browser Sandbox 是基于云原生函数计算的浏览器沙箱服务,为 AI Agent 提供安全、免运维的浏览器环境。通过 Serverless 架构与 CDP 协议支持,实现网页抓取、自动化操作等能力,并结合 VNC 实时可视化,助力大模型“上网”交互。
1154 43
|
7月前
|
数据采集 Web App开发 文字识别
基于 Python 的反爬虫策略突破与逆向技巧总结
本文介绍四大常见反爬策略及Python应对方案:User-Agent伪装、代理IP绕过IP限制、Tesseract识别验证码、Selenium模拟浏览器获取动态数据,助你合法、高效采集网页信息。
675 22
|
11月前
|
前端开发 Java 开发者
MVC 架构模式技术详解与实践
本文档旨在全面解析软件工程中经典且至关重要的 MVC(Model-View-Controller) 架构模式。内容将深入探讨 MVC 的核心思想、三大组件的职责与交互关系、其优势与劣势,并重点分析其在现代 Web 开发中的具体实现,特别是以 Spring MVC 框架为例,详解其请求处理流程、核心组件及基本开发实践。通过本文档,读者将能够深刻理解 MVC 的设计哲学,并掌握基于该模式进行 Web 应用开发的能力。
1912 1
|
Linux iOS开发 MacOS
【已解决】ModuleNotFoundError: No module named ‘IPython‘
【已解决】ModuleNotFoundError: No module named ‘IPython‘
|
算法 Linux
Linux copy时排除某文件/目录
Linux copy时排除某文件/目录
1172 0

热门文章

最新文章