你的 __init__ 竟然不是构造函数?——Python 对象创建的真正幕后黑手与致命误解
你的 __init__ 竟然不是构造函数?——Python 对象创建的真正幕后黑手与致命误解
在 Python 中,几乎每个开发者都是从 def __init__(self): 开始学习面向对象的。我们在里面给实例赋予属性,配置初始状态,理所当然地把它称为“构造函数”。然而,这个称呼其实是一个巨大的误解——__init__ 根本不是构造函数,它只是一个初始化方法。真正的构造函数是躲在幕后、鲜为人知的 __new__。这个误解看似无害,却导致了无数让人抓狂的“灵异事件”:在 __init__ 里 return 了什么,解释器却大声咆哮;继承不可变类型时,参数怎么传都不对;单例模式怎么也实现不了。
当你深入 Python 对象创建的底层机制时,你会发现 __new__ 与 __init__ 的关系就像“制造胚胎”和“赋予灵魂”——一旦混淆,轻则初始化失败,重则让你的代码出现难以追踪的副作用。今天,我们就来彻底揭开这个面向对象的核心面纱,理清 __new__ 和 __init__ 的职责与陷阱,让你真正掌控从无到有的对象创建过程。
一、问题复现:那些年我们栽过的“构造函数”坑
场景 1:试图在 __init__ 中返回一个不同的对象
class Example:
def __init__(self, name):
self.name = name
# 试图返回一个全新的对象?不行的!
return Example("default") # 错误!
e = Example("test")
# TypeError: __init__() should return None, not 'Example'
你本想当参数不符时返回一个默认实例,却直接触发了 TypeError。Python 明确告诉你:__init__ 必须返回 None。如果你不信邪,真的返回了别的对象,解释器会拒绝执行。
场景 2:继承不可变类型时,属性莫名其妙丢失
class UpperStr(str):
def __init__(self, s):
super().__init__(s)
self.original = s
u = UpperStr("hello")
print(u.upper()) # HELLO,字符串行为正常
print(u.original) # AttributeError: 'UpperStr' object has no attribute 'original'
你明明在 __init__ 里设置了 self.original,怎么一访问就不见了?因为 str 是不可变类型,真正的创建逻辑在 __new__ 中完成,而且 str.__init__ 可能并不会执行你所期望的初始化,或者实例的空间被严格限制,自定义属性根本无法挂载。
场景 3:单例模式用 __init__ 怎么也控不住
class Singleton:
_instance = None
def __init__(self):
if Singleton._instance is None:
Singleton._instance = self
self.value = 0
else:
# 试图返回已有的实例?无能为力
pass # 不能阻止 self 指向新实例
a = Singleton()
b = Singleton()
print(a is b) # False,不是同一个对象!
__init__ 无论如何都会作用于已经创建好的新实例。即便你在 __init__ 里检测到已经存在一个实例,你也无法阻止 self 指向新的对象。要控制实例的创建,必须拦截 __new__。
二、底层原理:__new__ 造胚胎,__init__ 赋灵魂
1. 对象创建的完整步骤
当执行 obj = MyClass(arg1, arg2) 时,Python 解释器在内部执行了以下操作:
- 调用元类的
__call__方法。通常这个元类是type,它的__call__实现了对象创建流程。 type.__call__首先调用MyClass.__new__(MyClass, *args, **kwargs)。__new__是一个静态方法(虽然不需要显式声明),负责创建并返回一个新的实例。它通常调用超类的__new__,比如object.__new__(cls),这会分配内存并返回一个空壳对象。
- 如果
__new__返回的是一个cls的实例(且__init__需要被调用),接着就会调用instance.__init__(*args, **kwargs)。__init__是一个实例方法,接收已经创建好的self,并对其进行初始化,设置属性等。
- 最后,
type.__call__将__new__返回的实例对象返回给调用者。
关键点:
__new__负责“生”:它分配内存,返回实例。如果它返回的不是当前类的实例,__init__将不会被调用。__init__负责“养”:它只能初始化已有的实例,不能创建新对象,并且不能返回任何非None值。
2. 为什么 __init__ 不能返回其他对象?
__init__ 的返回值在 Python 层被强制检查:如果返回的不是 None,会抛出 TypeError。这是因为它的职责仅仅是修改 self,而不是替换 self。如果你需要返回不同的对象,应该在 __new__ 中控制,__new__ 可以直接返回任意对象。
3. 不可变类型的特殊性
对于 int、str、tuple 等不可变类型,它们的实例一旦创建就无法修改。因此,真正的构造工作必然在 __new__ 中完成,例如 str.__new__(cls, s) 会创建实际的字符串对象。__init__ 在这些类型中可能什么都不做,或者根本不能修改实例内容。这解释了为何继承不可变类型时,你需要在 __new__ 中完成所有设置,并且自定义属性可能需要特殊处理(如使用 __slots__ 或继承自 object 的可变容器)。
4. 元类 __call__ 是总控制
如果你想在对象创建前后执行额外逻辑(例如统计实例数量、实现池化),可以自定义元类并重写 __call__。__call__ 可以决定是否调用 __new__ 和 __init__,甚至从缓存中返回已有实例。这是比 __new__ 更早的切入点。
三、常见陷阱与错误心理模型
陷阱 1:把 __init__ 当成 Java/C++ 的构造函数
在 Java 中,构造函数是与类同名的特殊方法,负责创建对象并初始化。但在 Python 中,你看到的只是初始化部分,创建早在 __new__ 里完成了。如果你在 __init__ 中尝试返回新对象、或认为 self 还没有值就大错特错——self 已经是一个完全有效的实例。
陷阱 2:在 __new__ 中忘记返回实例
__new__ 必须返回一个实例。如果你忘记返回,或返回了 None,解释器就不会调用 __init__,并且最终得到 None。
class Broken:
def __new__(cls):
print("创建?")
# 忘记 return cls 的实例
b = Broken()
print(type(b)) # <class 'NoneType'>
陷阱 3:在 __new__ 中调用 super().__init__(不必要)
__new__ 的职责是创建实例并返回。通常不需要在 __new__ 中调用 __init__,因为如果 __new__ 返回了当前类的实例,解释器会自动调用 __init__。手动调用可能导致重复初始化或混乱。
陷阱 4:在 __init__ 中使用 return 提前退出
如果你写 return 不带值(即 return 或 return None),这是允许的。但如果你写 return some_other_object,就会报错。
陷阱 5:以为 __init__ 是唯一的初始化入口
有时你需要在对象创建过程中进行一些计算,而这些计算必须在内存分配时完成(例如不可变对象的值)。此时必须使用 __new__ 来返回一个带有特定状态的实例。
四、正确使用 __new__ 和 __init__ 的黄金场景
场景 1:单例模式
通过 __new__ 控制实例的唯一性:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
# 初始化可以放在 __init__ 中,但注意每次调用都会执行 __init__
return cls._instance
def __init__(self, value=0):
self.value = value # 注意:每次 Singleton() 都会覆盖 value
实际生产环境中,可以在 __new__ 中初始化或者使用锁保护。
场景 2:继承不可变类型并添加额外属性
class FancyStr(str):
def __new__(cls, s, metadata=None):
instance = super().__new__(cls, s)
instance._metadata = metadata # 在 __new__ 中挂载,因为 __init__ 可能无法写
return instance
@property
def metadata(self):
return self._metadata
这样 FancyStr 实例既有字符串的值,也携带额外数据。
场景 3:实现工厂模式,根据参数返回不同子类实例
class Animal:
def __new__(cls, type):
if type == 'dog':
return super().__new__(Dog)
elif type == 'cat':
return super().__new__(Cat)
else:
raise ValueError
class Dog(Animal):
def __init__(self, type):
self.type = type
但是要注意,如果 __new__ 返回的不是 cls 的实例,__init__ 仍然会被调用吗?规则是:只有当 __new__ 返回的是 cls 的实例时,__init__ 才会被自动调用。如果返回其他类型的对象,__init__ 不会被调用。因此上面的例子可能需要调整:要么在 __new__ 中直接返回实例并手动初始化,要么让子类负责初始化。
场景 4:缓存对象(对象池)
在 __new__ 中检查对象池,返回已存在的对象,避免频繁创建销毁。
class Pooled:
_pool = []
def __new__(cls, *args, **kwargs):
if cls._pool:
return cls._pool.pop()
return super().__new__(cls)
def __init__(self, value):
self.value = value
@classmethod
def release(cls, obj):
cls._pool.append(obj)
(这里同样要注意 __init__ 每次都会调用,可能覆盖状态,需要结合设计。)
场景 5:修改 __new__ 以记录实例创建日志
class Logged:
def __new__(cls, *args, **kwargs):
print(f"正在创建 {cls.__name__} 的实例")
return super().__new__(cls)
def __init__(self, name):
self.name = name
这种用法无害,且能清晰看到 __new__ 先于 __init__ 执行。
五、调试与验证对象创建过程
- 打印顺序:在
__new__和__init__中分别print,观察调用顺序。 - 检查
type.__call__:如果你重写了元类的__call__,可以在里面加日志,追踪整个调用链。 - 使用
dis.dis可以反汇编类定义,但主要看__new__和__init__的字节码不是必须。 - 注意异常:如果
__new__抛出异常,__init__不会执行;如果__init__抛出异常,实例创建失败,但已经分配的内存可能会被垃圾回收,__del__可能不会调用。 - 静态分析工具:
mypy等工具会检查__init__的返回类型,但通常不深究__new__。
六、最佳实践总结
__new__用于控制实例的创建(单例、池化、不可变类型继承、返回不同类实例)。__init__用于初始化已创建的实例,绝对不要返回非None值。- 在普通类中,不要轻易重写
__new__,除非你确切知道自己在做什么。默认的object.__new__已经足够。 - 需要重写
__new__时,记得调用super().__new__(cls)并返回实例。 - 理解不可变类型的构造必须在
__new__中完成,并且__init__可能不被调用或无法修改状态。 - 在单例模式中,结合
__new__和__init__时,注意__init__会在每次获取实例时都执行,可能重置状态,通常应避免在单例中依赖__init__,而是在__new__中一次性初始化。 - 在文档中明确说明你的类是否重写了
__new__,以及为什么。 - 测试对象的创建行为,特别是继承不可变类型或使用了自定义
__new__的类,确保属性访问正常。
七、结语
Python 的 __init__ 并非构造函数,而是一台精密的“灵魂灌注机”;真正的“胚胎制造机”是隐藏得更深的 __new__。这个区分不是吹毛求疵的学究气,而是理解 Python 对象模型的核心钥匙。一旦你掌握了 __new__ 与 __init__ 的分工,那些“单例失效”、“属性丢失”、“返回异常”的怪事就会烟消云散。从今往后,不要再说“我写了个构造函数”,而要说“我定义了初始化方法”——这不仅是术语的准确,更代表着你对 Python 对象生命周期的彻底掌控。
更多推荐


所有评论(0)