K 的一隅

Python Python 语言核心

写下 Cla() 时发生了什么:对象创建顺序

从 Cla() 调用说起,拆分 __new__ 分配实例与 __init__ 初始化状态的先后;何时需要自定义 __new__。

7 分钟阅读 更新于 2026-07-28
本文目录

你在源码里写 Point(1, 2),字面上只是“调用 Point 并传两个参数”。在解释器眼里,这是一次可调用对象的调用,背后固定走一套创建协议:先造出空白 实例,再往上面挂初始属性。拆开看,核心是两步——__new__ 负责“有没有这个对象”,__init__ 负责“对象里 initially 有什么”。

很多教程把 __init__ 叫“构造函数”,容易让人以为对象是在 __init__ 里才诞生。实际上 __new__ 先返回实例__init__ 只是在已存在的对象上填字段;若 __new__ 决定不返回本类实例,__init__ 甚至不会被调用。记住这条时间线,读单例、不可变子类、对象池代码时会少很多困惑。

调用 Cla(...) 时的大致顺序

class Point 为例,Point(1, 2) 在概念上可以排成:

  1. 解析 Point 得到类对象(可调用)。
  2. 调用类的 __call__(通常由元类 type 提供),内部会:
    • 调用 Point.__new__(Point, 1, 2) → 得到新实例(尚未初始化或处于 partially initialized 状态)。
    • __new__ 返回的确实是 Point 的实例,再调用 Point.__init__(实例, 1, 2)
  3. 把最终实例作为表达式的值返回。
python
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__ 不会被调用。

python
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__

python
class Odd:
    def __new__(cls) -> object:
        return object()  # 不是 Odd 的实例

    def __init__(self) -> None:
        print("init 不会执行")


Odd()

这类写法极少见,但解释了某些工厂方法“只走 __new__”的原因。

init:初始化,不替代构造

__init__ 的第一个参数是 实例(惯例名 self)。它不应该返回非 None 的值;返回也没用,调用方拿到的仍是 __new__ 造出的那个对象。

python
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) 走到:

python
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__ 的典型情况:

  • 不可变类型的子类strinttuple 等实例创建后不能改内容,有时得在 __new__ 里完成构造。
  • 单例、对象池、缓存实例:控制“这个类同一时间只存在哪一个实例”。
  • 返回其他类型的对象:极少用;若 __new__ 返回的不是 cls 的实例,Python 会跳过 __init__
python
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__ 抛异常时对象可能已部分存在