写 Python 类的人几乎人人写过 def __init__(self, ...),教材里也常顺口叫它「构造函数」。但真要问「对象是谁造出来的」,答案不是它。
一句话结论:__init__ 不是造对象的人,它是「拿到一个已经造好的对象,往里填初始状态」的人。真正负责分配实例的是 __new__;把两者串起来的是 type.__call__,中间还有一道 isinstance 检查------__new__ 返回的要不是本类(或子类)实例,__init__ 会被整个跳过。
下面全部数据来自本机 CPython 3.13.14 实测。
一、先看调用顺序和那个 id
python
class A:
def __new__(cls, *a, **kw):
print(' A.__new__ cls=', cls.__name__, 'args=', a)
return super().__new__(cls)
def __init__(self, x):
print(' A.__init__ x=', x, 'id(self)=', id(self))
self.x = x
a = A(7)
实测输出:
css
A.__new__ cls= A args= (7,)
A.__init__ x= 7 id(self)= 2207591220624
a.x = 7 | type(a) = A
两点值得记:
__new__先拿到全部参数 ,包括后面要传给__init__的那份(这里args=(7,))。它不是无参的。__init__里的self不是新造的,就是__new__返回的那个对象。
再做一个更直白的验证:
python
class D:
def __new__(cls):
o = super().__new__(cls)
print(' D.__new__ created id=', id(o))
return o
def __init__(self):
print(' D.__init__ received id=', id(self))
D()
ini
D.__new__ created id= 2207591220960
D.__init__ received id= 2207591220960
两个 id 完全一样。所以 __init__ 的签名里写 self 而不是 return obj,是因为对象早就在那儿了,它只负责改。
二、__new__ 返回别的东西,__init__ 直接不跑
这是整套机制里最反直觉、也最能说明「谁才是造物主」的一条:
python
class B:
def __new__(cls, *a, **kw):
print(' B.__new__ -> returns an int (not a B)')
return 42
def __init__(self, x):
print(' !! B.__init__ SHOULD NEVER RUN')
b = B(9)
实测:
css
B.__new__ -> returns an int (not a B)
b = 42 | type(b) = int | __init__ called? NO
B(9) 得到了一个整数,而不是 B 的实例,__init__ 一次都没执行。
原因是 type.__call__ 在调完 __new__ 之后会做一次检查:只有返回值是当前类(或子类)的实例,才继续调 __init__ 。返回个 int,这一步直接短路,把 42 原样交回调用方。
这个行为不是 bug,是被明确利用的能力------工厂函数、缓存命中返回已有对象、单例、按参数返回不同子类,全靠它。
三、__init__ 的返回值被硬性限制
python
class C:
def __init__(self):
return 1
C()
javascript
TypeError: __init__() should return None, not 'int'
__init__ 只能返回 None(不写 return 就是 None)。因为调用方要的是 __new__ 造好的那个对象,不需要 __init__ 再交一个回来。这条限制反过来又印证了它的定位:它是修改器,不是构造器。
顺带一提,__new__ 没有这个限制------它必须返回点什么,否则 type.__call__ 拿不到对象,__init__ 也就无从谈起。
四、object.__new__ 的参数规矩
python
object.__new__(A, 1)
typescript
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
object.__new__ 只接受一个 cls。多传参数为什么报错?因为 CPython 有个保守判断:如果你重写了 __new__ 又重写了 __init__,多余参数会被放行;如果只重写了其中一个,多传参数就被认为「大概传错了」而报错。自己写 __new__(cls, *a, **kw) 时就不会撞这条。
五、真正需要 __new__ 的场景:不可变类型
普通类里改属性随时可以,但 int、str、tuple 这些不可变类型的值在实例诞生那一刻就定了,__init__ 里再改已经晚了。想给它们做子类并改值,只能在 __new__ 里动手:
python
class MyInt(int):
def __new__(cls, v):
return super().__new__(cls, v + 100)
print(MyInt(5))
实测输出:
scss
MyInt(5) = 105
5 进 __new__ 变成 105 后直接由 int.__new__ 造出实例,__init__ 全程没有插手余地。这是 __new__ / __init__ 分工最典型的用途,也是「教材把 __init__ 叫构造函数」最容易误导人的地方------对不可变类型,它连改都改不了。
六、把 type.__call__ 的链子摊开
A(7) 发生的事,用伪代码大约是这样:
python
def type_call(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs) # 1. 分配 / 造对象
if isinstance(obj, cls): # 2. 检查返回值类型
cls.__init__(obj, *args, **kwargs) # 3. 填初始状态(必须返回 None)
return obj
三个位置对应三种常见扩展点:
| 想干什么 | 该动哪里 |
|---|---|
| 控制实例怎么被创建、返回什么类型、做缓存/单例 | __new__ |
| 给已创建的实例填属性 | __init__ |
| 拦截整个调用过程(含参数预处理) | 元类的 __call__ |
| 子类被定义时自动登记 | __init_subclass__ |
顺带一个容易混的点:__init_subclass__ 是在类被定义 时触发的,跟实例创建无关;别把它和 __init__ 归成一类。
七、几个实用写法
单例 (靠 __new__ 缓存):
python
class OnlyOne:
_inst = None
def __new__(cls):
if cls._inst is None:
cls._inst = super().__new__(cls)
return cls._inst
按参数返回不同子类(工厂):
python
class Shape:
def __new__(cls, kind):
if kind == 'circle': return super().__new__(Circle)
return super().__new__(Square)
注意这类写法里 __init__ 会被跳过或重复调用,得自己把状态初始化放对地方,别指望它自动兜底。
八、排错清单(实测踩过的五条)
__init__里return了别的东西 →TypeError: __init__() should return None。把值赋给self.xxx,别 return。- 重写
__new__忘了return→ 得到None,后续isinstance检查失败,__init__不跑,调用方拿到None。 __new__返回了别的类型,__init__神秘不执行 → 不是 bug,是第 2 步的isinstance检查短路了。object.__new__(cls, extra)报「takes exactly one argument」 → 用def __new__(cls, *a, **kw)自己接住参数。- 给
int/tuple子类在__init__里改值无效 → 值在__new__阶段已固定,改到__new__里去。
九、小结
把这句话记住就够了:__new__ 造对象、__init__ 填状态、type.__call__ 串场,中间那道 isinstance 决定 __init__ 有没有资格跑。
所以下次有人问「Python 的构造函数是哪个」,比较准确的回答是:造对象的是 __new__;__init__ 是初始化器。C++ / Java 里那个「构造即分配」的直觉,在 Python 这里被拆成了两步,多出来的一步恰好就是很多高级写法(单例、工厂、不可变子类、元类)的入口。
运行环境:本机 CPython 3.13.14。本文所有输出均由 d53-probe.py 实测取得,未引用外部基准数据。