Python对象实例化过程中__init__是怎么被调用的
深入解析 Python 对象实例化全过程,阐述元类 type.__call__ 如何依次调度 __new__ 内存分配与 __init__ 属性初始化,以及 __init__ 调用的触发条件与底层运作机制。
在 Python 中执行 obj = MyClass(*args, **kwargs) 时,解释器并不会直接调用 __init__。类本身是元类(默认是 type)的一个实例,对类加括号调用实际上触发了元类中的 type.__call__(MyClass, *args, **kwargs)。这个调度器首先调用 MyClass.__new__ 在内存中分配并生成对象;随后校验该对象是否确属 MyClass 的实例;只有校验通过时,调度器才会把该对象作为第一个参数 self 传入并执行 MyClass.__init__ 完成初始化,最后将就绪的对象返回给调用方。
类调用与元类 __call__ 的总控调度
理解 __init__ 调用的首要前提,是厘清 Python 的对象模型。在 Python 体系中,“一切皆对象”,类本身也不例外。一个使用 class 关键字定义的类,本质上是内置元类 type 的一个具体实例。
在 Python 的底层机制中,任何对象只要实现了 __call__ 特殊方法,就可以像函数一样被加上圆括号调用。当我们写下:
obj = MyClass("example_arg")
此时被调用的对象是 MyClass 这个类对象。根据面向对象的方法查找规则,调用一个对象的方法,需要去该对象的“类型”(Type)中寻找定义。因为 MyClass 的类型是 type,所以 Python 解释器在底层实际执行的是:
obj = type.__call__(MyClass, "example_arg")
正是 type.__call__ 扮演了整个实例化过程的总导演角色。它依次安排了内存的申请、对象的创建、类型的安全检查以及 __init__ 初始化的调用。
__new__ 与 __init__ 的两步构建流水线
许多初学者容易把 __init__ 误称为“构造函数”,但实际上 Python 的对象构建被严格拆分为两道工序:真正的构造器是 __new__,而 __init__ 只是初始化器。
type.__call__ 接管调用后,构建流水线按如下规则推进:
- 第一步(构造):调用
cls.__new__(cls, *args, **kwargs)。这是一个静态方法(无需手动加@staticmethod),第一个形参是类对象自身(习惯命名为cls)。它的核心职责是在堆内存中为对象开辟物理空间并建立底层数据结构,通常通过super().__new__(cls)委托给object.__new__完成。该方法必须返回一个具体的对象实例。 - 第二步(类型检验与初始化):调用
instance.__init__(*args, **kwargs)。如果__new__成功返回了当前类的实例,type.__call__就会把这个刚刚诞生的空壳对象作为第一个形参self传递给__init__。此时对象已具备合法身份,__init__可以在self.__dict__中绑定各种业务属性与初始状态。
代码实证:跟踪两者的执行顺序与入参
下面的示例代码显式重写了 __new__ 和 __init__,并在关键节点打印调试信息,直观展现二者的交接过程:
class DemoTrace:
def __new__(cls, name, score):
print(f"[1] __new__ 被触发,当前类为: {cls.__name__}")
# 委托给根基类 object 分配真实内存空间
instance = super().__new__(cls)
print(f"[2] 实例已在内存中创建,id 为: {id(instance)}")
return instance
def __init__(self, name, score):
print(f"[3] __init__ 被触发,接收到的 self id 为: {id(self)}")
self.name = name
self.score = score
print(f"[4] 属性挂载完毕: {self.__dict__}")
# 触发实例化
demo = DemoTrace("Alice", 95)
[2] 实例已在内存中创建,id 为: 4381833040
[3] __init__ 被触发,接收到的 self id 为: 4381833040
[4] 属性挂载完毕: {'name': 'Alice', 'score': 95}
从输出中可以清晰确认:__new__ 中生成的对象物理地址与 __init__ 接收到的 self 地址完全一致。这证实了 __init__ 并没有凭空制造对象,它只是在装饰加工由 __new__ 交付的原材料。
关键分流:__init__ 为什么有可能被跳过?
在 Python 的设计规范中,__init__ 的触发并不是无条件的。type.__call__ 内部包含一个强制性的类型防线:
只有当
__new__ 返回的对象通过了 isinstance(instance, cls) 的判定(即返回的是当前类自身或其派生子类的实例)时,解释器才会执行 instance.__init__(*args, **kwargs)。若返回的是其他任何类型的对象,__init__ 将被完全跳过。
这种设计具有严密的工程安全考量:如果 __new__ 经由工厂逻辑或单例缓存返回了一个与当前类毫无关系的外部对象(例如返回了一个字典或字符串),如果依然盲目地把该外部对象当成 self 去执行当前类的 __init__,势必会向该对象胡乱注入当前类的属性,造成不可预知的状态污染或属性覆盖。
实测验证:__new__ 返回非当前类实例时的表现
通过故意让 __new__ 返回一个不匹配的对象,可以验证 __init__ 是否会被跳过:
class BypassInit:
def __new__(cls, *args, **kwargs):
print("[1] __new__ 执行:决定不创建当前类实例,而是返回一个独立的 dict 对象")
return {"status": "bypassed", "desc": "external dict"}
def __init__(self, *args, **kwargs):
# 如果类型校验失效,此处将被错误执行
print("[2] __init__ 被执行!(此行不应出现)")
self.bypassed = False
# 触发实例化调用
result = BypassInit()
print(f"[3] 最终返回对象类型: {type(result).__name__}, 内容: {result}")
[3] 最终返回对象类型: dict, 内容: {'status': 'bypassed', 'desc': 'external dict'}
运行结果证实:__init__ 完全没有被触发。由于 __new__ 返回的 dict 不是 BypassInit 的实例,调度器在比对 isinstance(result, BypassInit) 为 False 后,直接将该字典交还给外层调用者,确保了逻辑的独立与安全。
底层模拟与约束:用 Python 代码还原 type.__call__
为了让底层的调度过程更加具象透明,我们可以用纯 Python 语法写出等价于 CPython 底层 type_call 的逻辑还原:
def simulate_type_call(cls, *args, **kwargs):
"""
模拟 CPython 底层 type.__call__(cls, *args, **kwargs) 的调度过程
"""
# 1. 触发类定义的 __new__,负责申请空间与构造实例
instance = cls.__new__(cls, *args, **kwargs)
# 2. 关键类型约束检查
if isinstance(instance, cls):
# 3. 仅在类型契合时执行初始化
init_result = instance.__init__(*args, **kwargs)
# 4. 强制约束:__init__ 绝不允许返回除 None 之外的值
if init_result is not None:
raise TypeError(f"__init__() should return None, not '{type(init_result).__name__}'")
# 5. 返回由 __new__ 产出的实例对象
return instance
为什么 __init__ 必须严格返回 None?
从上面的模拟逻辑可以清楚地看到两点原因:
- 语义统一性:实例化的终极目标是获得
__new__所产出的对象。如果允许__init__自由返回一个值,就会在“__new__返回的对象”与“__init__返回的对象”之间产生歧义。 - 防止静默错误:若开发者在
__init__中顺手写了return self或return some_status,解释器不会容忍这种无效代码,而是立刻显式报错:TypeError: __init__() should return None, not '...',强制要求开发者通过属性或外部函数来传递处理状态。
核心对比与日常开发最佳实践
在日常 Python 业务开发中,绝大多数类只需要编写 __init__,几乎很少需要显式覆写 __new__。但在面对某些特殊架构设计时,明确二者的边界至关重要。
何时必须覆写 __new__?
- 继承不可变内置类型(Immutable Types):例如继承
int、str、float或tuple。因为不可变对象在__new__分配并成型后就不可再修改内部值,如果把参数传递放到__init__中修改,对象内部数据早已固化。因此必须在__new__中完成数值转换与基类构造。 - 实现单例模式(Singleton):当需要确保一个类在全局仅存在一个共享实例时,必须在
__new__中拦截创建动作,检查类属性中是否已存在缓存实例,若存在则直接返回该缓存,避免重复开辟内存。 - 对象池与缓存代理:在工厂模式或高并发连接池中,通过
__new__动态分流,根据传入参数决定返回已有连接、包装代理类还是全新实例。
何时使用 __init__?
- 常规状态配置:为对象赋予基础实例变量(如
self.timeout = 30)。 - 建立外部依赖连接:初始化数据库连接游标、绑定文件句柄、启动后台任务监听等。
- 业务有效性检验:对传入的业务参数进行校验并抛出
ValueError等常规异常。
总结而言,Python 中 __init__ 的调用是一套严谨协作体系的终点:由 type.__call__ 负责总控路由,__new__ 负责底层的物理构造,经过严格的类型匹配验证后,最终才交由 __init__ 完成业务属性的修饰与绑定。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









