__new__是单例的必经之路,因其在__init__前执行并负责返回实例,只有在此拦截才能确保复用同一对象;仅靠__init__会重复创建对象,违背单例语义。

Python 中的单例不能靠 __init__ 控制,必须拦截实例创建过程——__new__ 是唯一可靠入口;装饰器方案更灵活但要注意作用域和模块导入顺序。
为什么 __new__ 是单例的必经之路
__new__ 在 __init__ 之前执行,负责返回实例对象。单例的本质是“只返回同一个对象”,所以必须在这里判断并复用已有实例。如果只在 __init__ 里做检查,每次调用仍会新建对象,再覆盖属性,违反单例语义。
常见错误现象:id(A()) != id(A()) 却以为自己实现了单例;或在多线程下出现两个实例(没加锁)。
实操建议:
- 用类变量(如
_instance)缓存首次创建的实例 - 在
__new__开头检查该变量,存在则直接return,不调用super().__new__ - 多线程场景必须加
threading.Lock,否则if not cls._instance可能并发通过 - 注意:重写
__new__后,若父类不是object(如继承自list或dict),需确保调用正确的父类__new__
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
装饰器实现单例:更轻量,但作用域敏感
装饰器把单例逻辑从类定义中抽离,适用于不想修改原类、或需对多个类统一管理的场景。但它依赖闭包变量存储实例,而闭包作用域绑定的是装饰器定义时的模块——这意味着:同一装饰器跨文件使用时,每个模块会维护独立的单例缓存。
常见错误现象:在 module_a.py 和 module_b.py 中都用 @singleton 装饰类 DBClient,结果得到两个不同实例。
实操建议:
- 装饰器内部用字典按
cls键缓存,避免不同类冲突 - 若需全局唯一(跨模块),把缓存字典提到顶层模块(如
singleton_store.py)中管理 - 装饰器返回的应是类本身(不是新类),否则会丢失原类的
__name__、__doc__等元信息;可用functools.wraps修补,但对类装饰器效果有限,更推荐用types.SimpleNamespace或直接赋值 - 无法拦截子类继承行为——子类未显式应用装饰器时,不享受单例约束
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Config:
def __init__(self):
self.data = {}
两种方式的性能与兼容性差异
__new__ 方案启动快(无函数调用开销),但每个单例类都要重复写锁和缓存逻辑;装饰器方案复用性强,但每次实例化都多一层函数调用,且首次调用需构建闭包。
兼容性上:__new__ 对 typing.Generic、dataclass 或 NamedTuple 子类支持较差,容易因元类冲突报错;装饰器对这些更友好,但若类定义了 __call__,可能和装饰器逻辑打架。
实操建议:
- 简单工具类(如日志器、配置加载器)优先用装饰器,代码干净
- 需要严格控制构造流程(如初始化参数校验、资源预分配)或继承链复杂时,用
__new__更可控 - Pydantic v2+ 的
BaseModel类禁止重写__new__,此时只能用装饰器或依赖注入容器(如python-dependency-injector) - 测试时注意:单例状态会污染测试用例,务必在
setUp或pytest.fixture中重置缓存
真正难的不是写出“只生成一个实例”的代码,而是厘清这个“一个”究竟在哪个边界内有效:是当前解释器进程?当前模块?还是整个导入图谱?选哪种方式,本质上是在明确回答这个问题。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











