__new__ 是实现单例最稳妥的入口,因其在实例化最前端调用、早于 __init__,能真正控制是否创建新对象;需用类变量缓存实例、显式调用父类 __new__,并用 hasattr 防止重复初始化,多线程下需加锁。

为什么 __new__ 是实现单例最稳妥的入口
因为 __new__ 在对象实例化最前端就被调用,早于 __init__,能真正控制“是否创建新对象”。如果在 __init__ 里做判断,实例其实已经生成了,只是重复初始化——这不叫单例,叫“多次重置”。
-
__new__返回的是类实例,你可以在里面拦截并返回已存在的实例 - 必须显式调用父类的
__new__(即super().__new__(cls)),否则会报TypeError: object() takes no arguments - 不能在
__new__里调用self.xxx = ...,此时self还没完成构造,属性赋值应留给__init__
标准写法:带类变量缓存的 __new__ 实现
核心是用一个类变量(如 _instance)保存唯一实例,在 __new__ 中检查它是否存在。注意:这个变量必须定义在类体层级,不是方法内局部变量。
class Config:
_instance = None
<pre class="brush:php;toolbar:false;">def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
# 避免每次调用都重置
if not hasattr(self, '_inited'):
self.host = 'localhost'
self.port = 8000
self._inited = True
- 两次
Config()调用返回同一个对象,id(Config()) == id(Config())为True -
__init__里的初始化逻辑用hasattr(self, '_inited')守护,防止重复赋值 - 不加守护的话,每次实例化都会执行
__init__,导致字段被覆盖
多线程下 __new__ 单例会出问题吗
会。标准写法在并发场景中存在竞态条件:cls._instance is None 判断和 super().__new__(cls) 创建之间可能被多个线程同时穿过,导致创建多个实例。
立即学习“Python免费学习笔记(深入)”;
- 简单修复:用
threading.Lock包裹实例创建逻辑 - 更轻量方案:改用模块级单例(Python 模块天然单例),或用装饰器 +
functools.lru_cache(maxsize=1) - 不要依赖
if not cls._instance:,因为None、False、空列表等都为假,容易误判;坚持用is None
为什么不用装饰器或元类?什么情况下必须用 __new__
装饰器(如 @singleton)和元类也能实现单例,但它们作用在类定义层面,而 __new__ 是每个实例化动作的必经关卡——这意味着你能精确控制“哪些调用该走单例,哪些不该”。
- 需要支持带参数的单例(比如按配置名返回不同单例实例)时,
__new__可读取*args和**kwargs做路由 - 某些 ORM 或框架要求子类必须继承特定基类,且不允许替换类本身(装饰器会换掉原类),这时只能在
__new__内部做逻辑 - 元类写法复杂、调试困难,且对 IDE 类型推导不友好;
__new__更直观,也更容易单元测试
真正难的不是写出来,而是想清楚:这个单例要不要支持参数化?会不会被继承?有没有并发初始化风险?漏掉任何一个,就不是真单例。








