Python 单例模式:一个类只能有一个实例,到底怎么实现?
写程序的时候,有些东西全局只能有一份——配置管理器、日志记录器、数据库连接池。如果每个地方都 new 一个,资源浪费不说,状态还不一致。
单例模式就是干这个的:确保一个类在整个程序里只有一个实例,所有人都用它。
单例要解决的问题,先看一个反例
1 2 3 4 5 6 7 8 9
| class Config: def __init__(self): self.settings = {}
config1 = Config() config1.settings["theme"] = "dark"
config2 = Config() print(config2.settings)
|
config1 和 config2 是两个不同的对象,配置存到 config1 里,config2 拿不到。这就是问题——配置这种全局只有一份的东西,不应该被重复创建。
单例模式要的效果:
1 2 3
| config1 = Config() config2 = Config() print(config1 is config2)
|
最简单:模块级单例,利用 Python 的导入机制
Python 的模块在第一次被导入时执行,之后再导入直接用已经加载好的。这是最简单、最 Pythonic 的方式。
config.py:
1 2 3 4 5 6 7 8 9 10 11 12
| class Config: def __init__(self): self.settings = {} def set(self, key, value): self.settings[key] = value def get(self, key): return self.settings.get(key)
config = Config()
|
使用:
1 2 3 4
| from config import config
config.set("theme", "dark")
|
优点: 代码量最少,不需要任何技巧,Python 天然支持。
缺点: 不能延迟初始化(导入的时候实例就创建了),灵活性稍差。
适合场景: 日志记录器、配置管理、工具类等简单场景。大部分时候这就够了,不需要更复杂的实现。
_new_方式:控制实例的创建过程
如果你确实需要”调用类的构造函数来控制单例”,用 __new__。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Singleton: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self, value): if not hasattr(self, "_initialized"): self.value = value self._initialized = True
s1 = Singleton("first") s2 = Singleton("second")
s1 is s2 s1.value
|
关键点:
__new__ 在 __init__ 之前调用,负责”造出对象”
__new__ 返回什么,__init__ 就初始化什么
- 判断
_instance is None 决定是否创建,否则返回已有对象
- 但
__init__ 每次都会被调用,需要用 hasattr 或 _initialized 标志阻止重复初始化
适合场景: 类需要正常的构造函数调用方式(MyClass()),不想用模块级变量。
装饰器方式:不改类代码,加个装饰器就行
如果你不想修改类的内部实现,可以加个装饰器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def singleton(cls): instances = {} def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper
@singleton class DatabaseConnection: def __init__(self, url): self.url = url print(f"连接:{url}")
db1 = DatabaseConnection("mysql://localhost") db2 = DatabaseConnection("mysql://localhost") db1 is db2
|
优点: 不侵入类的内部代码,可以给多个类复用。
缺点: 装饰器包装后,类的类型信息可能会变(type(db1) 不再是 DatabaseConnection),需要额外处理。
适合场景: 你想给现有的类加单例行为,但不想改它的代码。
元类方式:从根源上控制类的行为
元类是”创建类的类”,控制类的实例化过程。用元类实现单例是最彻底的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 Config(metaclass=SingletonMeta): def __init__(self): self.settings = {}
c1 = Config() c2 = Config() c1 is c2
|
原理: 类的 __call__ 方法控制着 MyClass() 这个调用的行为。元类重写了 __call__,在每次”创建实例”时判断是否已经存在。
优点: 最彻底,支持继承(子类也会自动单例),行为最接近”类本身保证单例”。
缺点: 元类对初学者不太友好,理解成本高。
适合场景: 框架级别的基础类,或者你需要确保所有子类也都是单例的时候。
线程安全问题
上面的 __new__ 和元类实现,在多线程环境下可能出问题——两个线程同时判断 _instance is None,都认为该创建,结果造出两个实例。
加锁解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import threading
class ThreadSafeSingleton: _instance = None _lock = threading.Lock() 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
|
这叫”双重检查锁”,常见于多线程单例实现。
但要注意: 如果你的应用不是多线程的,不需要加锁,徒增开销。
一个完整的例子:数据库连接池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import threading
class DatabaseConnection: _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 def __init__(self): if not hasattr(self, "_initialized"): self.connections = [] self._initialized = True def get_connection(self): print("获取一个数据库连接") return "connection" def release_connection(self, conn): print("释放连接")
db1 = DatabaseConnection() db2 = DatabaseConnection() print(db1 is db2)
|