有一天,同事甩给我一段代码,让我看懂再说话。
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 特性~