面试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) 传的参数会同时透传给 __new__ 和 __init__。__new__ 接收的参数在 cls 后面,__init__ 接收的参数在 self 后面。如果两者的参数定义不一致,或者 __new__ 接收参数时出了问题,都会导致创建流程失败。最常见的例子是:__new__ 里处理了某些参数,但 __init__ 里的签名对不上,结果创建对象时的参数数量不匹配,直接抛 TypeError。
还有一点很多新手不理解:为什么 __new__ 的方法体里通常要写 super().__new__(cls)?因为 __new__ 的默认实现 object.__new__(cls) 才是真正分配内存并创建一个空实例的地方。如果你不调用 super().__new__(cls) 而是随便 return None 或者一个字符串,那整个创建过程就会翻车。手动定义 __new__ 时,要记住你的职责是返回一个实例,而最可靠的生成实例的方式就是调用父类的 __new__。
3. 那些反直觉的边界情况
3.1 不是每次创建都会触发 __init__
这是最反直觉的一个点。很多人以为只要写了 __new__ 和 __init__,两者就一定会依次执行。但事实是:当 __new__ 返回的实例不是当前类的实例时,__init__ 会被完全跳过。
python复制class Bar:
def __new__(cls, *args, **kwargs):
print("__new__ called")
return object.__new__(dict)
def __init__(self, *args, **kwargs):
print("__init__ called")
self.name = "bar"
b = Bar()
print(type(b))
print(hasattr(b, "name"))
运行结果:
code复制__new__ called
<class 'dict'>
False
Bar() 居然返回了一个 dict 实例,而且 __init__ 压根没执行。这听起来像Bug,但实际上是Python语言的明确规定:只有当 __new__ 返回的实例是当前类的实例(即 isinstance(instance, cls) 为真)时,解释器才会调用 __init__ 去初始化。这个特性在某些场景下非常有用。
举个实际的例子:假设你想做一个缓存池,当创建对象时,如果该对象已经存在就直接返回缓存,不再重新初始化。这时的思路就是让 __new__ 返回缓存中的对象,而缓存对象可能根本就不是当前类的实例,这样 __init__ 就不会被重复调用,不会把已有的状态覆盖掉。
3.2 __init__ 返回非 None 会直接报错
另一个容易踩的坑是 __init__ 的返回值。很多人写 __init__ 的时候,习惯性地写了 return self,这在Java或C++思维里好像是合理的,但在Python里是完全错误的。
python复制class Baz:
def __init__(self):
print("init start")
return self
baz = Baz()
运行结果:
code复制init start
TypeError: __init__() should return None, not 'Baz'
报错信息说得非常清楚:__init__ 应该返回 None。这是因为 __init__ 的返回值本来就没有任何意义,解释器在调用完它之后会把之前 __new__ 创建好的实例返回给调用者。如果你在 __init__ 里返回了其他值,解释器反而不知道该怎么处理了,干脆抛异常。这个错误信息我见过很多次,基本都是从其他语言转过来的开发者犯的。
3.3 手动调用时绕过 __init__
还有一种情况是把 __new__ 和 __init__ 的手动调用搞混。比如你可能会在某些代码里看到 Foo.__new__(Foo) 这种写法。这行代码会创建一个未经初始化的 Foo 实例——它存在,但所有属性都没设置。这就是 __new__ 和 __init__ 的分工体现:__new__ 只管创建,不管初始化。
这种手法在反序列化场景中特别常见。比如你从数据库或网络加载数据,想创建一个对象但不希望它的 __init__ 去执行那些重活(比如重新连接网络、读取配置文件),就可以用 object.__new__(Class) 先创建一个空壳,再手动填充属性。这在写ORM框架、缓存系统、恢复协议时非常有用。
3.4 拷贝操作和 __new__ 的关系
还有一个小小的边角知识:copy.copy 和 copy.deepcopy 创建的新对象,并不是通过调用类的 __new__ 来创建的,而是通过 __reduce_ex__ 协议来完成的。所以如果你在 __new__ 里做了某些特殊的注册操作,拷贝出来的对象不会经过 __new__。这算是一个比较深的坑,在实现需要唯一标识对象的类的时候容易遇到:你以为拷贝出的对象应该注册到某个全局列表里,结果发现压根没走这条路。
4. 实战场景解析
4.1 单例模式:最经典的 __new__ 应用
几乎每篇讲单例模式的文章都会用 __new__ 来实现,因为这是最直接、最清晰的思路:在类创建实例之前拦一道,如果已经有实例了就返回老对象,不再创建新的。
python复制class DatabasePool:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, host=None, port=None):
# 用标志位防止重复初始化
if not hasattr(self, "_initialized"):
self._initialized = True
self.host = host
self.port = port
pool1 = DatabasePool("127.0.0.1", 3306)
pool2 = DatabasePool("127.0.0.2", 3307)
print(pool1 is pool2) # True
print(pool1.host) # 127.0.0.1,注意第二次调用没有覆盖
这里有个极其容易踩的坑:单例模式下,虽然实例是同一个,但 __init__ 每次都会执行。如果 __init__ 里有赋值操作,第二次创建时就会覆盖掉第一次的值。所以我在 __init__ 里加了一个 _initialized 标志位,保证只初始化一次。
另外要注意线程安全。上面这段代码在多线程环境下是有问题的:两个线程同时进入 __new__,可能都发现 _instance is None,然后各自创建了一个实例。解决方式很简单,加锁就行。
python复制import threading
class SafeDatabasePool:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
在 __new__ 里做加锁操作比在 __init__ 里做要合适得多,因为创建实例的竞争发生在 __new__ 阶段。这个坑我在实际项目中踩过,当时用了一个共享连接池,在并发压测时发现生成了多个连接,排查到最后才发现是线程安全没处理好。
4.2 不可变对象的子类化
如果你想让自定义类表现得像 int、str、tuple 一样不可变,就必须重写 __new__ 而不是 __init__。为什么?因为不可变对象创建后就不能再修改内部状态了,而 __init__ 本质上是修改实例状态的。所以对于不可变对象的子类,只能通过 __new__ 在对象创建时一次性设定好所有属性。
python复制class UpperStr(str):
def __new__(cls, value):
return super().__new__(cls, value.upper())
def __init__(self, value):
# 这个不会被调用,或者即使调用也无法修改内容
pass
s = UpperStr("hello")
print(s) # HELLO
print(isinstance(s, str)) # True
注意 super().__new__(cls, value.upper()),这里的 super() 找到 str.__new__,后面的参数是传给 str 构造器的。对不可变类型来说,__new__ 才真正有实际意义。有一种特殊行为值得提一下:当你去子类化不可变类型时,如果只定义 __init__ 而不定义 __new__,你会发现传入的值压根没有生效,因为 __init__ 无法修改内部状态。
这就像买了一个成品家具,送货上门时已经组装好了,你没法通过"装修"改变它的结构,只能在"下单"的时候指定款式。__new__ 就是下单环节,__init__ 是收货后的布置环节。
4.3 元类编程中的 __new__
聊完 __new__ 在实例层面的用法,再往上走一层就是元类了。元类控制的是类的创建过程,所以它用的也是 __new__,不过是 type 的 __new__。
python复制class Meta(type):
def __new__(mcs, name, bases, attrs):
attrs["version"] = "1.0"
return super().__new__(mcs, name, bases, attrs)
class Service(metaclass=Meta):
pass
print(Service.version) # 1.0
这里的 Meta.__new__ 接收的 mcs 是元类本身,name、bases、attrs 分别是类名、基类、类属性字典。它和实例层面的 __new__ 很像:先获取到骨架(这里是把 name、bases、attrs 变成类的草稿),再做加工(向 attrs 里注入 version),最终调用父类的 __new__ 生成新的类对象。
如果把实例层面的 __new__ 和元类层面的 __new__ 放在对比的视角下看,会发现它们遵循同样的模式:创建一个更基础的"对象",然后返回它。只不过一个是创建实例,一个是创建类。理解了这一层,你对Python对象模型的理解就大大加深了。很多框架的口红——比如Django的Model元类、SQLAlchemy的DeclarativeMeta——都是基于这个机制工作的。
4.4 自定义创建流程:控制不想要 __init__ 的场景
有时候我们希望绕开 __init__ 来创建对象。比如一个 Report 类,它的 __init__ 需要读文件、解析内容,但当用户传入的是已经解析好的数据时,就不该重复执行这些步骤。这时候可以让 __new__ 根据参数类型决定走哪条路。
python复制class Report:
def __new__(cls, source, **kwargs):
if isinstance(source, dict):
# 已经解析好数据,直接创建一个空对象
instance = super().__new__(cls)
instance.data = source
return instance
return super().__new__(cls)
def __init__(self, source, **kwargs):
if hasattr(self, "data"):
return # data 已经在 __new__ 里设置好了
print(f"parsing {source}...")
self.data = f"data from {source}"
report = Report({"title": "hello"})
print(report.data) # {"title": "hello"}
这里的技巧是:在 __new__ 里对特定输入做短路处理,直接给空实例设置属性,这样 __init__ 再跑的时候检测到 data 已经存在就主动跳过。这种写法把"创建"和"初始化"的边界表达得很清楚,也是我实际项目里用过的一种优化手法。
5. 常见问题与排查技巧实录
前面讲了不少场景,现在把最常见的问题整理成一张速查表,方便你遇到问题的时候直接对照排查。
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
TypeError: __init__() should return None |
__init__ 返回了非 None 值 |
去掉 __init__ 中的 return 语句 |
__init__ 没被执行 |
__new__ 返回的不是本类实例 |
检查 __new__ 返回的对象类型,确保返回 cls 或 super().__new__(cls) |
| 创建对象时报参数不匹配 | __new__ 和 __init__ 的签名不一致 |
确保两者的参数定义一致,或者用 *args, **kwargs 来透传 |
| 单例对象被重复初始化 | __new__ 返回了同一个实例,但 __init__ 每次都执行 |
在 __init__ 里使用标志位或判断是否已初始化 |
| 多线程下单例失效 | 多个线程同时进入 __new__,同时发现无实例 |
在 __new__ 里加锁并双重检查 |
| 不可变对象子类化时值不变 | 只定义了 __init__,没有重写 __new__ |
重写 __new__ 完成初始化 |
RecursionError |
在 __new__ 里又调用了 cls() |
用 super().__new__(cls) 代替 cls() |
手动调用 __new__ 后实例无属性 |
__new__ 只创建空实例,不会触发 __init__ |
手动给实例设置属性,或不要绕过 __init__ |
上面表格里有一个 RecursionError 值得单独拿出来说。因为 __new__ 里的 cls() 会再次走 type.__call__,而 type.__call__ 又会调用 __new__,这样就会无限递归下去。正确做法是使用 super().__new__(cls),它走的是 object 的实现,不会递归。
还有几个我实际排查中遇到过的细节:
第一个是 object.__new__ 默认不接受额外参数。如果你写的 __new__ 是这样:
python复制class Demo:
def __new__(cls, x):
return object.__new__(cls, x) # 错误
运行时会报 TypeError: object.__new__() takes exactly one argument (the type to instantiate)。正确写法是把额外参数丢弃,只把 cls 传给 super().__new__(cls)。
第二个是 __new__ 和 __init__ 中 kwargs 的处理。如果类初始化时只接收关键字参数,要注意 **kwargs 在两者之间的传递。有一种写法是把所有参数定义在 __new__ 里,然后手动调用 super().__new__,再把参数塞给 instance.__init__,这样做容易出错。更稳妥的做法是让 __new__ 和 __init__ 签名保持兼容,统一用 *args, **kwargs 透传。
第三个是调试 __new__ 的时候,可以用 print 或 inspect 看看到底是谁在创建对象。我之前排查过一个诡异问题:一个类的实例总是被重复创建,最后发现是某个装饰器在类定义的时候隐式调用了类构造器。这种问题用传统的断点排查很难发现,但在 __new__ 里加个带调用栈的日志,瞬间就定位了。
6. 一个可能没注意过的工具类实践
说了这么多,最后再分享一个我写过的小工具。当你需要在代码里实现某种"按需创建"的模式,比如懒加载、连接池、缓存对象,__new__ 是个非常好的切入点。
假设你有一个 Connection 类,每次调用构造器都建立新的连接,代价很高。你可以加一个简单的缓存:
python复制class Connection:
_cache = {}
def __new__(cls, host, port, **kwargs):
key = (host, port)
if key in cls._cache:
return cls._cache[key]
instance = super().__new__(cls)
cls._cache[key] = instance
return instance
def __init__(self, host, port):
# 避免重复初始化
if getattr(self, "host", None) == host and getattr(self, "port", None) == port:
return
self.host = host
self.port = port
self._connected = True
conn1 = Connection("localhost", 8080)
conn2 = Connection("localhost", 8080)
print(conn1 is conn2) # True
这段代码的思路是:相同参数的连接,复用已有实例;不同参数才新建。__new__ 负责查缓存、建实例,__init__ 只负责初始化新实例的状态。通过 getattr 判断是否已经初始化过的做法,是懒加载模式里比较通用的一个技巧。
类似这样的工具类,在很多框架内部都能看到影子。理解 __new__ 的灵活运用,不仅能帮你写好自己的代码,也能让你在读框架源码时更快地理解对方的意图。
回到最初的问题:__init__ 和 __new__ 的区别到底是什么?其实一句话就能概括——__new__ 负责造出这个对象,__init__ 负责让这个对象变成它该有的样子。但这两者之间的边界远比一句话要复杂,只有把调用机制、返回值约束、边界情况、实战场景都吃透了,才算是真正掌握了它们。希望这篇文章能帮你把这层纸捅破。
