__new__ 是单例的关键入口,因它在实例创建前分配内存并返回对象;若仅在 __init__ 中判断,实例已多次生成。需用类级锁+实例缓存实现线程安全单例,并防护 __init__ 重复调用。

直接用 __new__ 实现单例,最稳妥的方式是加类级锁 + 实例缓存,否则多线程下大概率失效。
为什么 __new__ 是单例的关键入口
__new__ 在实例创建前被调用,负责返回一个对象;而 __init__ 每次都会执行。如果只在 __init__ 里做判断,已经 new 出多个实例了——根本没拦住。
-
__new__是真正“分配内存并返回对象”的地方,控制它才能控制“是否真新建” - 必须返回一个对象(通常是
super().__new__(cls)),否则实例化会失败 - 若已存在实例,直接返回它,跳过后续
__init__(但注意:__init__仍可能被重复调用,需额外防护)
基础写法:带缓存和线程安全的 __new__
不加锁的单例在并发场景下会创建多个实例,这是最常见的坑。
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
- 双检锁(Double-Checked Locking)避免每次进锁,提升性能
- 第一次
if判断避免无谓加锁;第二次在锁内确保唯一性 - 不加
_lock,两个线程同时卡在第一个if后,都进入super().__new__,就生成两个实例
如何防止 __init__ 被反复调用
即使 __new__ 返回了旧实例,Python 仍会无条件调用 __init__,导致初始化逻辑重复执行(比如重置状态、重新连接数据库)。
立即学习“Python免费学习笔记(深入)”;
- 常见做法:加标记位,在
__init__开头检查是否已初始化 - 不要在
__init__里写有副作用的初始化逻辑,除非你加了守卫 - 示例守卫写法:
if hasattr(self, '_initialized') and self._initialized: return,然后在首次__init__末尾设self._initialized = True
更实用的变体:支持参数传入的单例(如配置类)
如果单例需要接收初始化参数(比如 Singleton('prod')),就不能简单用 __new__ 无参判断——不同参数应视为不同实例,或需明确拒绝。
- 多数场景下,带参单例其实是反模式;建议把参数转为类属性或配置项,由单例内部读取
- 若必须支持,可对参数做哈希缓存(如
cls._instances[cls._key(args, kwargs)]),但要注意不可哈希类型(如 dict/list)需预处理 - 更推荐做法:禁止传参,强制通过 setter 或独立配置模块注入依赖
真正难的不是写出来,而是想清楚「这个单例是否真的该是全局唯一」——比如数据库连接池、日志器本身已有线程安全封装,硬套单例反而增加耦合和测试难度。


















