本文目录
你在源码里写 Point(1, 2),字面上只是“调用 Point 并传两个参数”。在解释器眼里,这是一次可调用对象的调用,背后固定走一套创建协议:先造出空白 实例,再往上面挂初始属性。拆开看,核心是两步——__new__ 负责“有没有这个对象”,__init__ 负责“对象里 initially 有什么”。
很多教程把 __init__ 叫“构造函数”,容易让人以为对象是在 __init__ 里才诞生。实际上 __new__ 先返回实例,__init__ 只是在已存在的对象上填字段;若 __new__ 决定不返回本类实例,__init__ 甚至不会被调用。记住这条时间线,读单例、不可变子类、对象池代码时会少很多困惑。
调用 Cla(...) 时的大致顺序
以 class Point 为例,Point(1, 2) 在概念上可以排成:
- 解析
Point得到类对象(可调用)。 - 调用类的
__call__(通常由元类type提供),内部会:- 调用
Point.__new__(Point, 1, 2)→ 得到新实例(尚未初始化或处于 partially initialized 状态)。 - 若
__new__返回的确实是Point的实例,再调用Point.__init__(实例, 1, 2)。
- 调用
- 把最终实例作为表达式的值返回。
class Point:
def __new__(cls, x: float, y: float) -> "Point":
print(f"__new__: cls={cls.__name__}")
instance = super().__new__(cls)
return instance
def __init__(self, x: float, y: float) -> None:
print(f"__init__: x={x}, y={y}")
self.x = x
self.y = y
p = Point(3.0, 4.0)
# __new__: cls=Point
# __init__: x=3.0, y=4.0
print(p.x, p.y) # 3.0 4.0不写 __new__ 时,继承自 object 的默认 __new__ 仍会执行——只是你看不到打印而已。
用时间线概括(同一行输出顺序固定):
Point(3, 4)
→ type.__call__(Point, 3, 4) # 类被调用
→ Point.__new__(Point, 3, 4) # 分配实例
→ Point.__init__(实例, 3, 4) # 写 self.x, self.y
→ 返回实例new:分配实例
__new__ 是静态方法语义(历史上用 __new__ 定义时不自动绑 self):第一个参数是 类 cls,返回值必须是该类的实例(或子类实例),否则 __init__ 不会被调用。
class Singleton:
_instance: "Singleton | None" = None
def __new__(cls) -> "Singleton":
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, tag: str) -> None:
self.tag = tag
a = Singleton("first")
b = Singleton("second")
print(a is b) # True
print(a.tag) # second —— __init__ 每次都会跑这个例子说明:__new__ 控制“是否新建”,__init__ 每次 Singleton(...) 仍会执行——单例常要在 __init__ 里加“只初始化一次”的守卫,或把逻辑挪到 __new__。
若 __new__ 返回的对象不是 cls 的实例(例如返回已有单例、或返回 None),Python 跳过 __init__:
class Odd:
def __new__(cls) -> object:
return object() # 不是 Odd 的实例
def __init__(self) -> None:
print("init 不会执行")
Odd()这类写法极少见,但解释了某些工厂方法“只走 __new__”的原因。
init:初始化,不替代构造
__init__ 的第一个参数是 实例(惯例名 self)。它不应该返回非 None 的值;返回也没用,调用方拿到的仍是 __new__ 造出的那个对象。
class User:
def __init__(self, name: str, *, active: bool = True) -> None:
self.name = name
self.active = active
u = User("Ada", active=False)
print(u.name, u.active) # Ada False类型在 __new__ 返回后已经定好;__init__ 只往 实例 __dict__(或 slots)里写属性。上一篇讲的实例属性,几乎都在这里完成赋值。
子类若重写 __init__,仍应调用 super().__init__(...),否则父类那层初始化不会自动跑——但父类的 __new__ 若没被子类覆盖,仍会在最具体子类的 __new__ 链里通过 super().__new__(cls) 走到:
class Base:
def __init__(self) -> None:
self.base_ready = True
class Derived(Base):
def __init__(self, tag: str) -> None:
super().__init__()
self.tag = tag
d = Derived("v1")
print(d.base_ready, d.tag) # True v1何时需要自定义 new
大多数类只写 __init__ 就够。需要动 __new__ 的典型情况:
- 不可变类型的子类:
str、int、tuple等实例创建后不能改内容,有时得在__new__里完成构造。 - 单例、对象池、缓存实例:控制“这个类同一时间只存在哪一个实例”。
- 返回其他类型的对象:极少用;若
__new__返回的不是cls的实例,Python 会跳过__init__。
class TaggedStr(str):
def __new__(cls, value: str, tag: str) -> "TaggedStr":
obj = super().__new__(cls, value)
obj.tag = tag # str 子类在 __new__ 之后挂额外属性(演示用)
return obj
t = TaggedStr("hello", tag="greeting")
print(t, t.tag) # hello greeting不可变类型子类的细节在魔术方法篇章还会遇到;这里只要记住:immutable 的“真构造”往往发生在 __new__。
子类与 object.new
最底层 object.__new__(cls) 在 CPython 里负责分配空壳 实例;用户类几乎总是 return super().__new__(cls),让继承链最终落到 object。若忘记 return,__new__ 默认返回 None,随后 __init__ 会对 None 调用而报错——自定义 __new__ 时务必显式 return 实例。
框架代码里偶尔见到“禁止直接实例化”的类:把 __new__ 写成 raise TypeError(...),或 __init__ 里校验必须由工厂方法创建。这类模式都依赖你理解:实例 是否存在,取决于 __new__ 是否返回了合法对象,而不是 __init__ 有没有跑完。
再补一条继承场景:子类定义 __new__ 时,几乎总要 return super().__new__(cls),否则可能跳过 object 的分配逻辑;子类只改 __init__ 则通常足够,因为 __new__ 仍沿 MRO 找到父类 / object 的实现。不要在 __init__ 里尝试“再创建一次 self”——self 已经存在,你要做的是初始化,不是替换身份。
和 call、元类的边界
Point(1, 2) 实际是“类对象被调用”。类的 __call__ 由元类实现,普通用户类很少重写。再上一层,类的创建(class Point: 那一行)才涉及元类的 __new__——那是另一条时间线,留到元类一篇。
同一次 Cla(...) 里,传给 __new__ 和 __init__ 的实参是同一套(除 cls / self 外)。因此 Point(3, 4) 里,__new__(cls, 3, 4) 与 __init__(实例, 3, 4) 参数对齐;若你在 __new__ 里改写了签名(例如只接收部分参数),要确保与 __init__ 仍匹配,否则容易出现“分配成功但初始化参数对不上”的异常。
常见误解
__init__不是 malloc:名字像 constructor,但 C/Java 意义上“分配内存”的是__new__。- 重写
__init__不阻止继承链上的__new__:子类没写__new__仍用父类 /object的版本。 - 在
__init__里return something_else:无效;别这么写。 - 以为
__init__失败会撤销已分配的对象:若__init__抛异常,实例可能已部分初始化,通常由调用方处理异常;不会自动“回滚”__new__已分配的对象。 - 子类只写
__init__就改了分配逻辑:分配仍走继承链上的__new__;要改“造不造、造几个”,得动__new__。
把 __new__ → __init__ → 返回实例 记成固定顺序,看到框架里的工厂、单例、不可变子类,就能对号入座;下一篇会把“谁都能加括号调用”扩成完整的可调用协议——Point(1, 2) 正是 类 作为 callable 被调用的典型例子。
小结:创建链上的角色
| 步骤 | 谁负责 | 典型问题 |
|---|---|---|
Cla(...) | 类的 __call__(元类提供) | 为何 class 能加括号 |
__new__ | 分配 / 决定是否新建 实例 | 单例、不可变子类 |
__init__ | 初始化 实例 属性 | 字段默认值、校验 |
| 返回 | 调用方得到 实例 引用 | 类型已固定 |
整条链在单次表达式里完成;你写的 __init__ 只是其中最后一环,却承载了大部分业务语义。
若你在调试器里单步跟 Cla(),会看到先进入 __new__ 再进 __init__(若定义了打印或断点)。partially initialized 对象指 __init__ 尚未跑完、但 实例 已存在的状态——此时别的线程若读到该对象,可能看到字段未齐备;一般业务代码很少在多线程里半初始化就暴露 实例,但理解时序有助于解释某些竞态日志。
对比 __new__ 与 __init__ 的职责,可以记一句口诀:new 给壳,init 填肉。壳一旦造错类型或返回 None,后面的 __init__ 要么不跑,要么跑在错误对象上——排查构造 bug 时从 __new__ 的返回值看起,往往比纠结 __init__ 里哪行赋值更快定位。
CPython 里 object.__new__ 会分配内存并设置 实例 的类型指针;之后 __init__ 无论成功失败,这块内存通常已由 __new__ 占住。理解这一点,就能解释为何 __init__ 抛异常时对象可能已部分存在。