用__new__实现单例时实例重复创建但状态没变,是因为未在__new__中返回已存在的实例,而是继续调用super().__new__()新建对象;且__init__每次仍执行,需加守卫防止重复初始化。

用 __new__ 实现单例时,为什么实例重复创建但状态没变?
因为 __new__ 控制对象构造,但很多人忘了在它里面缓存实例并跳过后续初始化。典型错误是只在 __new__ 里判断是否已有实例,却没返回已存在的那个,而是继续调用 super().__new__,结果每次仍新建对象。
- 必须在
__new__开头检查类属性(如_instance)是否存在,存在就直接return它 - 不存在才调用
super().__new__(cls)创建新实例,并赋值给类变量 -
__init__仍会被每次调用——所以初始化逻辑要加守卫,比如用if not hasattr(self, '_inited'): - 多线程下不加锁会出问题,
threading.Lock要包住整个__new__的判断+创建逻辑
示例关键片段:
class Singleton:<br> _instance = None<br> _lock = threading.Lock()<br><br> def __new__(cls):<br> if cls._instance is None:<br> with cls._lock:<br> if cls._instance is None:<br> cls._instance = super().__new__(cls)<br> return cls._instance
装饰器实现单例,为什么函数式写法比类装饰器更常用?
函数式装饰器(闭包)天然隔离作用域,每个被装饰的类独享自己的实例缓存;而类装饰器若没处理好,容易把不同类的实例混在同一个字典里。
- 核心是用闭包变量存
{cls: instance}映射,而不是用全局 dict 或类变量 - 装饰器返回的内层函数要检查缓存,命中则返回,否则创建并缓存
- 注意装饰器不能直接套在类上再调用
__init__——得确保返回的是实例,不是类本身 - 如果被装饰类有
__init__参数,装饰器必须支持传参(用*args, **kwargs),否则报TypeError: __init__() takes 1 positional argument but X were given
简版示意:
def singleton(cls):<br> instances = {}<br> def get_instance(*args, **kwargs):<br> if cls not in instances:<br> instances[cls] = cls(*args, **kwargs)<br> return instances[cls]<br> return get_instance<br><br>@singleton<br>class Database:<br> pass
两种方式在继承场景下谁更稳?
__new__ 方式默认不兼容继承——子类调用 super().__new__ 时可能绕过父类的单例控制;装饰器方式只要正确实现,对子类透明,更稳妥。
- 用
__new__时,子类若重写__new__但没显式复用父类逻辑,单例就失效 - 装饰器作用于类定义本身,子类即使不显式装饰,只要没覆盖父类单例实例,也能共享(取决于具体实现)
- 如果需要子类各自独立单例(比如
MySQLConnection和PostgreSQLConnection各自单例),装饰器必须按cls键区分缓存,__new__则需在每个子类里单独维护_instance
Python 3.8+ 用 typing.Singleton 能替代手写吗?
不能。typing.Singleton 只是类型提示,不提供运行时行为,连 import 都不会触发单例逻辑。
- 它仅用于静态类型检查工具(如 mypy)标记“这个类型应只存在一个实例”
- 实际运行仍需手写
__new__或装饰器,否则毫无单例效果 - 混淆这点会导致上线后发现多个实例在跑,而类型检查却一路绿灯
真正要用,还是得老老实实控制构造过程——单例不是声明出来的,是拦下来的。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











