__new__是单例的关键入口,因为它在实例创建前被调用且必须返回实例对象,可决定是否复用已有实例;而__init__每次都会执行,无法阻止重复初始化。

为什么 __new__ 是单例的关键入口
因为 __new__ 在实例创建前就被调用,且必须返回一个实例对象——它才是真正“决定要不要造新东西”的地方。而 __init__ 每次都会执行,没法阻止重复初始化。
常见错误是只在 __init__ 里加判断,结果每次调用 MyClass() 仍会新建对象再初始化,完全没节省资源。
实操要点:
-
__new__必须显式调用父类的__new__(通常是super().__new__(cls)),否则返回None导致实例为None - 要用类变量(如
_instance)缓存已创建的实例,且首次检查必须在调用父类__new__前完成 - 不能在
__new__里调用self.xxx,此时self还不存在;所有状态初始化仍应放在__init__中(但需配合判断避免重复)
基础单例实现:带线程安全考虑
最简版本能跑,但在多线程下可能创建多个实例——因为两个线程同时判断 cls._instance is None 都为真,接着都执行 super().__new__(cls)。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 用
threading.Lock包裹实例创建逻辑,确保检查 + 创建原子化 - 锁对象本身要定义为类变量,且只初始化一次(推荐在类定义内用
Lock()) - 注意:锁不能放在
__init__里,否则起不到作用
示例片段:
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
带参数的单例:为什么直接传参会出问题
如果单例类的 __init__ 接收参数(比如配置路径、数据库 URL),直接写 Singleton("db1") 会导致后续调用 Singleton("db2") 时,__init__ 被再次执行,覆盖原有状态——但实例仍是同一个,造成逻辑混乱。
根本原因:__new__ 不接收除 cls 外的参数,所有传给构造函数的参数最终都流向 __init__。
解决思路:
- 禁止构造时传参,改用显式初始化方法(如
init_config(path)),并在内部加if not hasattr(self, '_inited')判断 - 若必须支持首次构造传参,可在
__new__中缓存参数,并在__init__中比对:首次调用才真正处理,后续忽略 - 更稳妥的做法是把参数作为类属性或配置中心的一部分,而非构造函数输入
继承单例类时容易忽略的陷阱
子类直接继承单例父类后,如果不重写 __new__,默认会复用父类的 _instance 缓存——这意味着 ChildA() 和 ChildB() 可能共享同一个实例,违反直觉。
典型错误现象:isinstance(ChildA(), ChildB) 居然为 True。
实操要点:
- 每个子类需要独立维护自己的
_instance和_lock,不能共用父类变量 - 推荐用
cls._instance(而非Singleton._instance)做判断和赋值,依赖动态cls - 如果希望强制子类也单例,应在父类
__new__中使用cls作为键,用字典管理不同类的实例,例如_instances[cls] = ...
这个细节一旦漏掉,调试时很难定位——日志里看构造函数被调用了,但对象行为却像另一个类的实例。


















