面试N次,几乎每次都会被问到一个看起来简单、但很少有人能讲透的问题:__init__ 和 __new__ 到底有什么区别?如果你只是背过结论,比如"__new__ 在 __init__ 之前调用""一个是构造方法一个是初始化方法",那大概率会在追问细节的时候露怯。这俩方法确实是Python对象系统里最容易混淆的一对,也是很多坑的根源。
今天这篇就用实际案例把这两个方法彻底拆开,从底层调用机制到各种反直觉的边角情况,再到单例、不可变对象、元类这些实战场景,一次说清楚。不管你是刚入门想搞懂基础概念,还是写了好几年Python想补上这块短板,看完应该都能有个完整的认识。
1. 先从角色定位说起:谁在"造",谁在"养"
很多资料喜欢用房子来打比方:__new__ 是盖房子的人,他负责把毛坯房搭起来;__init__ 是装修的人,负责往毛坯房里添置家具和生活用品。这个类比方向是对的,但很多人没点透最关键的一层关系:__new__ 必须先盖好房子,装修师傅才有地方干活。换句话说,__init__ 依赖 __new__ 的产出,而 __new__ 完全不依赖 __init__。
在Python的对象生命周期里,__new__ 是真正负责创建实例的方法。它接收的第一个参数是类本身(通常命名为 cls),而不是实例(self),因为在调用它的时候,实例还不存在。它要做的事情是返回一个全新的实例对象。如果它不返回东西,那整个创建过程就失败了,__init__ 压根不会被执行。
__init__ 接收的第一个参数是已经创建好的实例(self),它的职责是初始化这个实例的状态,比如给属性赋值、建立连接、注册回调等等。这里有个特别容易被忽略的点:__init__ 方法必须返回 None。如果你让 __init__ 返回了其他值,Python会直接抛 TypeError,这个后面展开讲。
所以两者的本质区别并不是"一个先执行一个后执行"这么简单,而是职责完全不同:一个负责分配内存、创建实例,一个负责填充状态、完成初始化。正因为职责不同,它们在实际使用中的写法、返回值要求、调用频率也完全不同。
在实际项目里,99%的类只需要定义 __init__,因为默认的 object.__new__ 已经足够应付正常创建实例的需求。那什么时候需要动 __new__?答案是你需要控制"实例的创建过程本身"的时候,比如实现单例模式、不可变对象的子类化、自定义反序列化逻辑等场景。后面我会用具体代码演示这些场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 底层执行机制:一次完整的对象创建流程
很多人在看到 MyClass() 这个语法的时候,以为它直接调用了 __init__。这是最多人搞错的地方。MyClass() 实际上触发的是一个更复杂的调用链。
先看一段最简单的代码:
python复制class Foo:
def __new__(cls, *args, **kwargs):
print("__new__ called")
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print("__init__ called")
self.value = args[0] if args else None
foo = Foo(42)
# 输出:
# __new__ called
# __init__ called
从输出顺序可以看得很清楚:__new__ 先执行,__init__ 后执行。但这背后到底发生了什么?关键在于Python中一个隐藏的魔术方法:type.__call__。
当我们执行 Foo(42) 时,实际上Python在背后做了这样的事:实例对象 Foo 是元类 type 的实例,所以 Foo(42) 本质上是调用 type.__call__(Foo, 42)。type.__call__ 的内部逻辑大致如下:
- 调用
Foo.__new__(Foo, 42)来创建实例。 - 检查
__new__返回的结果。如果返回的实例是Foo的实例(严格说是isinstance关系成立),就继续调用Foo.__init__(instance, 42)。 - 如果
__new__返回的不是Foo的实例,那__init__就不会被调用。 - 最后把创建好的实例返回给调用者。
这就是为什么 __new__ 通常被称为"构造方法"而 __init__ 被称为"初始化方法"——一个真正构造对象的骨架,一个只是往骨架里填血肉。
这里有个知识点必须掌握:__new__ 虽然在定义上不需要加 @staticmethod 装饰器,但它在Python解释器层面就是被当作静态方法处理的。也就是说,你调用它的时候,Python会自动把类作为第一个参数传进去,但这个方法本身不会自动收到实例。你可以把它理解为语言层面的"钦定"静态方法。
再来看参数传递:Foo(42) 传的参数会同时透传
