当前位置:首页>python>我写了五年 Python,才发现对象不是 __init__ 创建的

我写了五年 Python,才发现对象不是 __init__ 创建的

  • 2026-10-11 07:31:42
我写了五年 Python,才发现对象不是 __init__ 创建的

有一天,同事甩给我一段代码,让我看懂再说话。

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

a = Singleton()
b = Singleton()
print(a is b)

输出是 True。

我盯着这段代码看了三分钟,觉得有什么地方不对劲——__init__ 去哪了?__new__ 是什么东西?对象不是应该在 __init__ 里创建的吗?

当时我没吱声,私下搜了一通,才发现自己对 Python 对象创建机制的理解,一直停在门口。


真正创建对象的,是 __new__,不是 __init__

这是我那天最大的认知修正。

很多人(包括那时的我)以为对象的生命周期从 __init__ 开始。其实不是,__init__ 只是"初始化"——它接收的 self,已经是一个创建好的对象了。

真正负责"把对象从无到有造出来"的,是 __new__。

完整流程是这样的:

class Foo:
    def __new__(cls, value):
        print(f"__new__ 被调用,cls={cls}, value={value}")
        instance = super().__new__(cls)
        print(f"对象在这里创建了:{instance}")
        return instance

    def __init__(self, value):
        print(f"__init__ 被调用,self={self}, value={value}")
        self.value = value

f = Foo(42)

输出:

__new__ 被调用,cls=<class '__main__.Foo'>, value=42
对象在这里创建了:<__main__.Foo object at 0x...>
__init__ 被调用,self=<__main__.Foo object at 0x...>, value=42

__new__ 先执行,创建对象,返回对象实例,Python 再把这个实例作为 self 传给 __init__。

顺序是:__new__ → __init__。


那 __new__ 有什么用?

普通情况下,你不需要碰它。但有三个场景,__new__ 是唯一的解法:

场景一:单例模式

整个进程只能有一个实例,典型的比如数据库连接池、配置管理器。

class DatabasePool:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            print("第一次创建,初始化连接池")
            cls._instance = super().__new__(cls)
            cls._instance.connections = []
        return cls._instance

    def __init__(self):
        pass  # 注意:__init__ 每次都会被调用,所以初始化逻辑要放在 __new__ 里

pool1 = DatabasePool()
pool2 = DatabasePool()
pool3 = DatabasePool()

print(pool1 is pool2)  # True
print(pool2 is pool3)  # True
print(id(pool1) == id(pool3))  # True

输出:

第一次创建,初始化连接池
True
True
True

三次实例化,只真正创建了一次对象。

有个要注意的坑:__init__ 每次都会被调用,哪怕 __new__ 返回的是同一个旧实例。所以如果你在 __init__ 里每次都重置状态,单例就白做了。


场景二:不可变对象的定制

int、str、tuple 这些内置不可变类型,继承之后如果想改变初始值,必须用 __new__,因为等到 __init__ 的时候值已经固定了。

class PositiveInt(int):
    def __new__(cls, value):
        if value <= 0:
            raise ValueError(f"PositiveInt 只接受正整数,你传了 {value}")
        return super().__new__(cls, value)

a = PositiveInt(10)
print(a)        # 10
print(type(a))  # <class '__main__.PositiveInt'>
print(a + 5)    # 15

b = PositiveInt(-3)  # 直接报错

输出:

10
<class '__main__.PositiveInt'>
15
Traceback (most recent call last):
  ...
ValueError: PositiveInt 只接受正整数,你传了 -3

这个模式做自定义类型验证非常优雅——继承 int 但加上业务约束,用起来和普通 int 无缝兼容。


场景三:对象缓存池(Flyweight 享元模式)

当你有大量重复的轻量对象,不想每次都 new 一个新的:

class Color:
    _cache = {}

    def __new__(cls, name):
        if name not in cls._cache:
            instance = super().__new__(cls)
            instance.name = name
            cls._cache[name] = instance
            print(f"创建新颜色对象:{name}")
        else:
            print(f"复用缓存颜色:{name}")
        return cls._cache[name]

c1 = Color("red")
c2 = Color("blue")
c3 = Color("red")  # 复用

print(c1 is c3)  # True,同一个对象
print(len(Color._cache))  # 2,只有两种颜色被创建过

输出:

创建新颜色对象:red
创建新颜色对象:blue
复用缓存颜色:red
True
2

一个经典问题:__new__ 返回的不是当前类的实例会怎样?

这是个有趣的边界情况,__init__ 不会被调用:

class Weird:
    def __new__(cls, value):
        print("__new__ 被调用")
        if value < 0:
            return "负数不配有对象"  # 返回字符串,不是 Weird 实例
        return super().__new__(cls)

    def __init__(self, value):
        print("__init__ 被调用")
        self.value = value

w1 = Weird(10)    # 正常流程
w2 = Weird(-1)    # __new__ 返回字符串
print(type(w1))   # <class '__main__.Weird'>
print(type(w2))   # <class 'str'>

输出:

__new__ 被调用
__init__ 被调用
__new__ 被调用
<class '__main__.Weird'>
<class 'str'>

__new__ 返回非当前类实例时,Python 就跳过 __init__。这个机制本身不常用,但理解它有助于搞清楚整个对象创建的底层逻辑。


总结一下

__new__ 是对象的"出生",__init__ 是对象的"装修"。

大部分时候你只需要装修,__new__ 系统自动帮你建好房子。但有时候你需要定制建造过程本身——控制只盖一套房子(单例)、控制建材规格(不可变类型约束)、或者把旧房子直接翻出来用(缓存复用)——这时候就得自己动手写 __new__。

理解这个机制之后,很多之前觉得"黑魔法"的代码,突然就看懂了。

最后,别忘了关注「有为大青年」,我们下期见~

觉得这篇有收获的话,点个赞、转发给身边学 Python 的朋友,也欢迎在留言区聊聊你遇到过哪些"以为懂了但其实没懂"的 Python 特性~

最新文章

随机文章