__new__ 是单例的关键入口,因它在对象创建前执行并返回实例,而 __init__ 每次都调用;只在 __init__ 判断会重复创建对象,违背单例语义。

直接用 __new__ 实现单例最稳妥,但必须小心处理多线程、继承和参数传递问题——否则第一次调用正常,后续带参数实例化会报 TypeError: __init__() takes 1 positional argument but X were given。
为什么 __new__ 是单例的关键入口
__new__ 在对象创建前执行,负责返回实例;而 __init__ 每次都会被调用。如果只在 __init__ 里做判断,实例其实已经新建了,浪费资源且破坏单例语义。
实操建议:
- 在
__new__中检查类属性(如_instance)是否存在,不存在才调用super().__new__(cls) - 必须返回实例对象,不能只写逻辑不写
return - 不要在
__new__中调用__init__,那是 Python 自动做的
带参数的单例必须跳过重复初始化
常见错误:用户写了 MySingleton('a', 'b') 两次,第二次进 __init__ 时仍会用新参数覆盖已有状态,或直接抛错(因为 __init__ 被重复调用但实例已存在)。
正确做法是让 __init__ 只运行一次:
- 加类级标记(如
_initialized = False),首次__init__后设为True - 后续进入
__init__直接return - 注意:这个标记要放在类上,不是实例上(否则每个实例都重置)
多线程环境下必须加锁
两个线程几乎同时判断 _instance is None,都通过并各自执行 super().__new__,结果生成两个实例。
解决方式(推荐最简方案):
- 用
threading.Lock()包住__new__中的判空 + 创建逻辑 - 锁对象定义为类变量(
_lock = threading.Lock()),确保所有实例共享同一把锁 - 避免用
functools.lru_cache或装饰器方案——它们无法控制__init__行为,且对带参数类不透明
继承单例类时容易踩的坑
子类没重写 __new__,会沿用父类逻辑,导致所有子类共享同一个父类实例(即 A() 和 B() 返回同一对象),这通常不是想要的。
若需支持继承且各自单例:
- 把
_instance改成字典:_instances = {},key 为cls - 在
__new__中用cls查字典,而不是硬编码父类名 - 锁也要按类区分,或统一用一个全局锁(更简单,性能影响极小)
真正麻烦的从来不是写几行 __new__,而是想清楚“单例”到底作用于哪个粒度:整个类?每个子类?是否允许不同参数构造不同实例?这些决策一旦定错,后面加锁、加缓存、加类型检查全是补丁。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











