如果你写过一段时间的 Python,多半见过 __init__,但凡面试或者看框架源码,又躲不开 __new__。这两个双下划线方法都跟实例化有关,但一个是“创建对象”,一个是“初始化对象”,很多人学了几年也没彻底分清楚。这篇东西我按自己的理解把它们的区别、调用顺序、典型场景、踩坑点全部捋一遍,包含可直接运行的验证代码,适合刚接触魔法方法的进阶玩家,也适合准备 Python 面试的开发者收藏。
先给一句总纲:__new__ 负责“造”出对象,__init__ 负责“修”好对象。造不出来,后面想修都没得修;造出来的东西不对类型,__init__ 甚至根本不会执行。后面所有细节都围绕这句话展开。
1. 先搞清楚这两个方法在对象生命周期里的位置
1.1 实例化过程不是你以为的“一步到位”
很多人写 obj = Foo() 的时候,脑子里默认这是“调用了 Foo 的构造方法”。其实这一步远没有这么简单。Python 里 Foo() 这个表达式,底层会先走 type.__call__(Foo, *args, **kwargs),然后在这个调用过程中依次触发 Foo.__new__ 和 Foo.__init__。换句话说,Foo() 是一个面向用户的入口,真正的“构造”分成了两个阶段。
为什么要拆成两半?因为有些对象根本不需要“初始化”,只需要“创建”。最典型的就是不可变对象,比如 int、str、tuple。你还记得 tuple 一旦创建就不能改吧?如果你先创建一个空对象再往里塞数据,那不可变就无法保证了。所以 tuple 这类对象必须在 __new__ 阶段就把数据定死,__init__ 干脆不做事。理解了这一点,你就理解了为什么 Python 要在 __init__ 之外单独设计一个 __new__。
用一个粗糙的生活类比:__new__ 相当于工厂里的“开模压胚”,把一个空壳子做出来;__init__ 相当于“组装零部件、贴标签”,往壳子里装东西。开模这一步只能做一次,装零件可以在不同阶段反复做,但必须先有壳子。这个顺序是硬性的,Python 解释器规定 __new__ 先执行,且必须返回一个实例,然后 __init__ 才会拿这个实例去初始化。
1.2 __new__ 是“造人”,__init__ 是“给身份证”
从职责上看,__new__ 的第一个参数是类本身,也就是 cls,它负责返回一个实例对象。你可以在里面做各种拦截,比如决定“这个类不直接生成新实例,而是返回之前缓存的实例”,或者“不允许直接实例化,抛异常”。这些操作都在对象诞生之前发生。
而 __init__ 的第一个参数是实例本身,按惯例叫 self,它的职责是给这个已经存在的实例设置属性。它不需要返回值,如果写了返回值,Python 会直接抛 TypeError。这一点和很多其他语言的构造函数完全不一样,其他语言里构造函数通常允许返回对象,但 Python 里 __init__ 必须返回 None。
从参数上也能看出分工:__new__(cls, ...) 接收的是类,__init__(self, ...) 接收的是实例。同一个 Foo(),传入的参数会分发给这两个方法。这个细节稍后会有代码演示。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心区别逐条拆解:参数、返回值、触发时机
2.1 参数传递的差异
写一个最普通的类:
python复制class Foo:
def __new__(cls, name, age):
print("__new__ 被调用,cls =", cls)
return super().__new__(cls)
def __init__(self, name, age):
print("__init__ 被调用,self =", self)
self.name = name
self.age = age
f = Foo("张三", 18)
print(f.name, f.age)
输出:
code复制__new__ 被调用,cls = <class '__main__.Foo'>
__init__ 被调用,self = <__main__.Foo object at 0x...>
张三 18
注意,Foo("张三", 18) 里的 "张三" 和 18 同时传给了 __new__ 和 __init__。所以如果你重写了 __new__,签名必须和 __init__ 兼容,否则会报参数错误。很多新手在这里翻车,只给 __new__(cls, *args, **kwargs) 而不透传参数。
更省事的写法是 __new__(cls, *args, **kwargs),然后内部调用 super().__new__(cls)。这样不管外部传入什么参数,都能原样透传给 __init__,因为你没有在 __new__ 里消费这些参数。但如果你在 __new__ 里用了这些参数,比如要根据参数决定要不要创建新对象,那就要小心处理。
2.2 返回值对后续流程的影响
__new__ 的返回值决定了 __init__ 会不会被调用。规则是这样的:
- 如果
__new__返回的是当前类cls的实例,那么__init__会被自动调用。 - 如果
__new__返回的是其他类的实例,那么当前类的__init__不会被调用。 - 如果
__new__没有返回实例(比如返回None),那么Foo()的返回结果就是None,而且__init__也不会被调用。
这个规则很多人不知道,但它是理解 __new__ 的核心。写个例子:
python复制class Bar:
pass
class Foo:
def __new__(cls):
print("__new__ 返回 Bar 实例")
return Bar()
def __init__(self):
print("这个不会执行")
f = Foo()
print(type(f))
输出:
code复制__new__ 返回 Bar 实例
<class '__main__.Bar'>
Foo() 返回了一个 Bar 对象,Foo.__init__ 完全没被调用。这在“对象池”“缓存复用”场景里非常有用:如果你希望多次调用 Foo() 返回同一个已存在的对象,并且不希望重新初始化,就可以让 __new__ 直接返回缓存对象。但要注意,如果缓存对象是 Foo 的实例,__init__ 依然会被再次调用,这可能会带来副作用。后面专门讲这个坑。
2.3 触发时机和优先级
画一条时间线的话,Foo() 的执行顺序是:
- 调用
type.__call__(Foo, ...)。 type.__call__内部调用Foo.__new__(Foo, ...)。- 如果
__new__返回的是Foo的实例,自动调用Foo.__init__(instance, ...)。 Foo()表达式得到最终的对象。
__new__ 本质上是静态方法,但 Python 会让你直接写成实例方法的样子,不需要手动加 @staticmethod。解释器会特殊处理它,自动把类作为第一个参数传入。这也是为什么很多人一开始觉得它长得怪。
__init__ 是标准的实例方法,它拿到的第一个参数就是 __new__ 的返回值。如果 __new__ 返回了对象但没来得及初始化,比如返回了一个 cls() 创建的新对象,那 __init__ 也可能会被调用两次?不会。如果你在 __new__ 内部直接使用 cls(...),那就是递归创建,会再次走 __new__ 和 __init__,非常容易导致无限递归。正确做法是使用 super().__new__(cls) 或者 object.__new__(cls)。
3. 实战场景:什么时候必须重写 __new__
3.1 实现单例模式
面试里最常见的一道题:用 Python 实现单例。很多人的第一反应是改 __init__,加一个标志位防止重复初始化。其实真正的单例应该控制的是“实例创建”,而不是“初始化”。来看看经典写法:
python复制class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
self.value = value
这里 __new__ 保证了不管调用多少次 Singleton(),拿到的都是同一个对象。但有一个隐患:__init__ 每次都会被调用,所以 value 会不断被覆盖。如果你希望只初始化一次,得再加判断:
python复制class Singleton:
_instance = None
_initialized = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
if not Singleton._initialized:
self.value = value
Singleton._initialized = True
很多人觉得麻烦,于是干脆用装饰器或者元类实现单例。但不管哪种方式,底层核心其实都是拦截“实例创建”,而不是“初始化”。所以理解 __new__ 是理解单例实现的前提。
3.2 缓存复用与对象池
除了单例,__new__ 还常用于有上限的缓存池。比如限制某个类最多只能有 3 个实例,超出后复用最早创建的实例,或者根据参数返回已有实例。典型例子是内部使用 __new__ 实现枚举效果:
python复制class Color:
_cache = {}
def __new__(cls, rgb):
if rgb not in cls._cache:
instance = super().__new__(cls)
instance.rgb = rgb
cls._cache[rgb] = instance
return cls._cache[rgb]
这段代码的意义在于:相同 rgb 值调用 Color() 时,返回的是同一个对象,节省内存。但注意,这里我们在 __new__ 里直接给 instance.rgb = rgb 赋值了,因为如果等 __init__ 再去赋值,每次调用都会覆盖同一份属性,那也没问题,但可能会导致“第二次调用时对象已存在,却仍执行初始化”的困惑。
3.3 不可变对象和自定义子类
int、str、tuple 这类不可变类型的子类,想在创建时定制内容,重写 __init__ 是无效的,因为底层对象在 __new__ 阶段就已经确定了。比如:
python复制class UpperStr(str):
def __new__(cls, value):
return super().__new__(cls, value.upper())
这里没有定义 __init__,因为在 __new__ 阶段就把字符串变成大写了。如果尝试用 __init__ 去做,会发现对象已经创建好了,无法修改。同理,当你想给 tuple 子类添加字段时,也只能在 __new__ 里处理。
这类场景是 __new__ 无法被替代的地方,也是面试官最爱追问的点:为什么重写不可变类型要用 __new__?答案就是:不可变对象创建后就不能改了,所有初始化数据必须在创建那一刻传进去。
3.4 元类(Metaclass)中的 __new__
元类也有 __new__,但它的作用对象是类,不是实例。比如:
python复制class Meta(type):
def __new__(mcs, name, bases, namespace):
print("创建类", name)
return super().__new__(mcs, name, bases, namespace)
这里的 __new__ 在类创建时被调用,负责“创建类”这个对象。很多 ORM 框架和接口校验工具就是靠这个在类定义阶段注入方法或校验字段。它和实例的 __new__ 虽然同名,但上下文不同,理解时可以分开记忆:类的实例化用 type.__call__ → 类的 __new__ → 类的 __init__;而类本身的创建用的是元类的 __new__。
4. 实操对比与细节坑:验证、排查要点
4.1 一段代码验证完整规则
把前面的规则集中到一个脚本里,跑一遍就能直观看到区别:
python复制class A:
def __new__(cls, *args, **kwargs):
print("A.__new__")
return super().__new__(cls)
def __init__(self):
print("A.__init__")
class B:
def __new__(cls, *args, **kwargs):
print("B.__new__ return None")
return None
def __init__(self):
print("B.__init__")
然后执行:
python复制a = A()
print("A 实例:", a)
b = B()
print("B 结果:", b)
输出会显示 A.__init__ 被调用,B.__init__ 不会被调用,b 是 None。这说明如果你的 __new__ 忘记返回对象,创建会静默失败,而不是报错。这种问题非常隐蔽,代码可能运行很久不报错,但某个功能一直返回 None,查起来费劲。
再看 __init__ 返回值的问题:
python复制class C:
def __init__(self):
print("C.__init__")
return 123
实例化 C() 时,Python 会直接抛出:
code复制TypeError: __init__() should return None, not 'int'
所以一个简单的记忆法是:__init__ 永远不要写 return,或者最多写 return None。
4.2 继承场景下容易踩的坑
当你写一个子类并重写 __new__ 时,最常见的错误是调用了错误的父类。比如:
python复制class Base:
pass
class Child(Base):
def __new__(cls):
return super().__new__(cls)
这段代码没问题,super().__new__(cls) 会沿着 MRO 找到 object.__new__,为 Child 分配实例。但如果你图省事写成 Base.__new__(cls),在复杂继承下可能出问题。为什么?因为 Base 可能也有自定义的 __new__ 逻辑,直接绕过可能会跳过重要步骤。
另一个坑是 object.__new__ 对参数有限制。如果你在 __new__ 里写了 object.__new__(cls, name),会报 TypeError,因为 object.__new__ 只接受一个类参数。正确做法是:
python复制def __new__(cls, *args, **kwargs):
return super().__new__(cls)
或者干脆 object.__new__(cls),把多余参数留在外部,反正 __new__ 之后 __init__ 会拿到它们。
还有一点:如果父类的 __new__ 有必须传的关键字参数,子类重写时必须显式处理,否则会出现“参数传不到父类”的怪问题。排查时建议在 __new__ 和 __init__ 里都打印 *args、**kwargs,能很快定位参数消失在哪一环。
4.3 小心“缓存对象被重复初始化”
前面提到过,__new__ 返回已有实例时,__init__ 依然会被调用。这会导致一个隐蔽 bug:你辛辛苦苦实现了对象池,每次拿到的都是同一个对象,但属性每次都被重置,其他调用方的数据被覆盖。看个例子:
python复制class Pool:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, data):
self.data = data
p1 = Pool([1, 2, 3])
p2 = Pool([4, 5, 6])
print(p1 is p2) # True
print(p1.data) # [4, 5, 6],已经变了
如果你期望 p1 保持 [1, 2, 3],那就必须在 __init__ 里加“是否已初始化”判断,或者把初始化逻辑挪到 __new__ 里,再想办法阻止 __init__ 执行(比如让 __new__ 返回非本类实例,但这会破坏类型)。更干净的做法是引入一个 _initialized 标志。生产级对象池很少直接用 __new__ 裸写,通常配合工厂函数或元类,所以这块更像一个“理解机制”的演练。
5. 常见问题速查与实用建议
5.1 面试高频问题整理
这里我把经常被问到的几个问题整理成表,方便快速过一遍:
| 问题 | 答案要点 |
|---|---|
__new__ 和 __init__ 有什么区别? |
__new__ 负责创建实例,__init__ 负责初始化实例;前者返回实例,后者返回 None。 |
先执行 __new__ 还是 __init__? |
先 __new__ 再 __init__,且仅当 __new__ 返回当前类实例时才自动调用 __init__。 |
__new__ 的第一个参数是什么? |
类本身 cls,即使没有加 @staticmethod 也会自动传入。 |
什么场景必须用 __new__? |
不可变类型子类定制、单例/对象池、元类中控制类创建。 |
__init__ 能不能返回非 None? |
不能,会抛 TypeError。 |
__new__ 返回 None 会怎么样? |
__init__ 不执行,类() 表达式返回 None。 |
这些问题本身不难,但背后还经常带一个追问:__init__ 能拦截实例创建吗?答案是不能,因为调用到 __init__ 时实例已经存在。想拦截创建,只能在 __new__ 里动手。
5.2 日常开发里该不该重写 __new__
我的建议是:能不用就不用。绝大多数业务代码里,__init__ 已经足够。用 __new__ 的场景很明确:
- 你要控制“唯一性”、“数量上限”或“复用”,用缓存/单例/对象池。
- 你要继承不可变类型,比如
str、tuple、int,并且需要修改创建逻辑。 - 你在写元类,需要干预类对象的生成。
- 你要实现自定义的“不可变对象”属性语义。
如果不是这些情况,重写 __new__ 只会增加代码理解成本。而且一旦写了 __new__,就必须保证返回正确的实例,否则调试成本极高。另外,如果 __new__ 里做了耗时操作(比如查缓存、读文件),每次 类() 调用都会触发,这可能是性能隐患,需要额外设计缓存策略。
5.3 一个小技巧:利用 __new__ 限制实例数量
最后分享一个我实际用过的玩法:限制某个类最多只能创建 N 个实例,超出后就返回最早那个。这个需求在连接池、任务队列场景里偶尔会出现。
python复制class Limited:
_instances = []
_limit = 3
def __new__(cls, *args, **kwargs):
if len(cls._instances) < cls._limit:
instance = super().__new__(cls)
cls._instances.append(instance)
else:
instance = cls._instances[0]
return instance
def __init__(self, index):
self.index = index
a = Limited(1)
b = Limited(2)
c = Limited(3)
d = Limited(4)
print(a is d) # True,超出限制后复用第一个
print([x.index for x in Limited._instances])
注意这里的 d.index 被 __init__ 改成了 4,因为 d 实际上就是 a,再次初始化覆盖了属性。要避免这种副作用,还是得加初始化标志,或者把初始化放到 __new__ 里并保证只创建时赋值一次。具体取舍取决于业务。
我在实际项目中用到 __new__ 最多的地方是封装第三方 SDK 的数据库连接对象:希望通过同一个连接位多次获取时返回同一个 client,避免反复握手。当时最大的教训就是只重写了 __new__ 没管 __init__,导致每次获取连接都重置了超时配置,排查了半天才发现是初始化覆盖的问题。所以如果你决定用 __new__ 做缓存,一定先想清楚“缓存对象被再次初始化”会带来什么后果,要么接受覆盖,要么显式拦截。这种经验不亲自踩一次坑,光看文档很难体会。
