定义
在系统中保证某个对象在生命周期内只有一个实例,并提供全局访问点。
注意两个关键词:
- 唯一性(Only One Instance)
- 可访问性(Global Access)
它解决的不是“方便创建对象”,而是:
对“共享状态 / 共享资源”的集中管理问题。
针对的工程场景
单例只在以下场景下是合理的:
表示“系统级资源”
例如:
- 配置加载器(Config)
- 日志系统(Logger)
- 连接池(Pool 管理器)
- 硬件 / OS 资源抽象
这些对象的共同点是:
- 多个实例没有任何语义意义
- 多实例反而会导致资源冲突或状态不一致
表示“全局一致状态”
例如:
- 运行模式(debug / prod)
- 全局策略开关
- 系统能力描述(capability)
这类对象不是“业务对象”,而是运行环境的一部分。
除此之外,在 Chatbot 环境中,负责核心事件处理逻辑的类(比如 Muika-After-Story 中的 事件循环)也常用单例模式,因为角色的状态在不同事件处理中是一致的。
实现
标准的实现方法
class Singleton: _instance = None # Singleton._instance 作为一个类属性保留在当前模块的命名空间(`Singleton.__dict__`)中,用于存储当前类的单例
def __new__(cls, *args, **kwargs): # *args, **kwargs 会传入 __init__ 方法中 if cls._instance is None: cls._instance = super().__new__(cls) # -> object.__new__(cls) 向内存申请空间,创建一个当前类的实例 & 裸对象 return cls._instance # 这个类已经初始化为一个对象了,返回这个对象的引用a = Singleton()b = Singleton()assert a is b线程安全 & 全局初始化一次实现
import threading
class Singleton: _instance = None _lock = threading.Lock() _initialized = False # 实际上 __init__ 会在 __new__ 之后调用,因此每一次获取单例都会重复调用一次 __init__ 方法
def __new__(cls, *args, **kwargs): if cls._instance is None: with cls._lock: if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance
def __init__(self, *args, **kwargs): if self.__class__._initialized: # 因此要添加初始化标记来防止重复初始化 return
...关于 __new__ 方法的说明:
在 Python 中 __new__() 是新式类中才有的方法,它执行在构造方法创建实例之前。可以这么理解,在 Python 中类中的构造方法 __init__() 负责将类实例化,而在 __init__()启动之前,__new__() 决定是否要使用该 __init__() 方法(因为 __new__() 可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例)。
通常来说,新式类开始实例化时,__new__() 方法会返回cls(cls指代当前类)的实例,然后该类的 __init__() 方法作为构造方法会接收这个实例(即self)作为自己的第一个参数,然后依次传入 __new__() 方法中接收的位置参数和命名参数。
注意:如果 __new__() 没有返回cls(即当前类)的实例,那么当前类的 __init__() 方法是不会被调用的。如果 __new__() 返回其他类(新式类或经典类均可)的实例,那么只会调用被返回的那个类的构造方法。
__new__() 方法的特性:
__new__()方法是在类准备将自身实例化时调用。- 正如你所见,
__new__()方法始终都是类的静态方法,即使没有被加上静态方法装饰器。
如果要得到当前类的实例,应当在当前类中的 __new__() 方法语句中调用当前类的父类的 __new__() 方法。
例如,如果当前类是直接继承自 object,那当前类的 __new__() 方法返回的对象应该为:
def __new__(cls, *args, **kwargs): ... return object.__new__(cls)模块级单例(推荐)
class Config: ...
config = Config()from config import config这是 Python 事实上的单例模式:
- import 只执行一次
- 语义清晰
- 不需要魔法
这样写起来非常简单,不需要额外重写 __init__ 和 __new__ 方法。
装饰器 / 元类单例(不推荐)
class SingletonMeta(type): _instances = {}
def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls]
class Singleton(meta=SingletonMeta): ...现代 Python 已经很难看到用元类定义的类实现了。所以不推荐
代价
这是大多数教程刻意回避的部分。
隐式全局状态
Config().debug = True任何地方都能改,但改动路径不可追踪。
测试极其困难
- 状态会在测试间泄漏
- 必须手动 reset
- 并行测试容易互相污染
强耦合、反向依赖
代码表面上:
Logger().info("...")实际上是:
- 依赖被隐藏
- 无法通过参数传递替换实现