Python 中的单例模式(Singleton Pattern)
单例模式是一种创建型设计模式,其核心目的是:确保一个类全局只有一个实例,并提供一个全局访问点来获取该实例。
常见应用场景:
- 数据库连接池
- 日志器(Logger)
- 配置管理器(Config Manager)
- 线程池
- 缓存对象
单例模式的优点
- 节省资源(只创建一次)
- 全局统一访问
- 控制实例数量
缺点
- 难以单元测试(全局状态)
- 可能隐藏依赖关系
- 在多线程环境下需小心实现
Python 中实现单例模式的几种常见方式
Python 是动态语言,实现单例非常灵活。下面从简单到推荐依次介绍。
1. 使用模块级变量(最 Pythonic,最推荐!)
Python 模块本身就是天然的单例,所有导入模块的代码共享同一个实例。
# singleton.py
class _Singleton:
def __init__(self):
print("Singleton created")
def do_something(self):
print("Doing something...")
instance = _Singleton() # 模块级实例,只创建一次
# 在其他文件中使用
# from singleton import instance
from singleton import instance
print(instance) # 同一个对象
instance.do_something()
优点:简单、无需额外代码、线程安全、天生单例
这是大多数 Python 项目中实际使用的“单例”方式。
2. 使用 __new__ 方法重写(经典方式)
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):
# 注意:__init__ 可能会被调用多次!
# 如果需要初始化逻辑,要防止重复执行
if not hasattr(self, '_initialized'):
print("Initializing singleton...")
self._initialized = True
# 测试
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True
print(id(s1), id(s2)) # 相同地址
注意:__init__ 会被调用多次(每次实例化时),所以初始化逻辑要加保护。
3. 使用装饰器实现
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class MyClass:
def __init__(self, value):
self.value = value
# 使用
a = MyClass(10)
b = MyClass(20)
print(a is b) # True
print(a.value) # 10(第一次传入的值生效,后续忽略)
4. 使用元类(Metaclass)实现(高级方式)
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class DatabaseConnection(metaclass=SingletonMeta):
def __init__(self):
print("Connecting to database...")
# 使用
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True
5. 线程安全的单例(使用锁)
如果在多线程环境中使用 __new__ 方式,建议加锁避免竞争:
import threading
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
# 双重检查(Double-Checked Locking)
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
推荐总结
| 方式 | 推荐度 | 线程安全 | 简洁性 | 说明 |
|---|---|---|---|---|
| 模块导入 | ★★★★★ | 是 | 最高 | 最 Pythonic,强烈推荐 |
__new__ 重写 | ★★★★ | 需加锁 | 高 | 经典方式 |
| 装饰器 | ★★★ | 是 | 中 | 灵活但参数会被忽略 |
| 元类 | ★★ | 是 | 低 | 强大但复杂 |
| threading.Lock | ★★★ | 是 | 中 | 多线程必备 |
实际建议
- 99% 的场景下,使用模块级单例就够了,不要过度设计。
- 如果你真的需要一个“类”的单例行为,使用
__new__或元类。 - 避免在单例中持有可变全局状态(容易导致 bug)。
如果你想看单例在实际项目中的应用(如日志器、配置中心),或者结合其他模式(如工厂+单例),可以告诉我!