Python3 单例模式:确保类仅有一个实例
单例模式(Singleton Pattern)是一种常用的设计模式,它确保一个类在整个程序生命周期中只创建一个实例,并提供一个全局访问点。这种模式适合用于配置管理、日志记录、数据库连接等场景,避免资源浪费或状态不一致。
本文将介绍 Python 中实现单例模式的多种方法,及其适用场景和优缺点。
单例模式的核心思想
单例模式的核心目标是:
- 类只能有一个实例
- 类必须自行创建这个实例
- 类必须向整个程序提供这个实例的全局访问点
简单来说,无论尝试创建多少次类的实例,都只会得到同一个对象。
1 2 3 4 5 6 7 8 9
| class Singleton:
s1 = Singleton() s2 = Singleton()
print(s1 is s2)
|
Python 实现单例模式的常用方法
方法 1:使用 __new__
方法(最常用)
__new__
是 Python 中用于创建实例的特殊方法(在 __init__
之前调用)。通过重写 __new__
,可以控制实例的创建过程。
使用__new__
方法实现单例模式
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
| class Singleton: _instance = None
def __new__(cls, *args, **kwargs): if not cls._instance: 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")
print(s1 is s2) print(s1.value) print(s2.value)
|
关键点:
_instance
类属性存储唯一实例
__new__
方法控制创建逻辑:只在首次调用时创建实例
- 需要处理
__init__
被多次调用的问题(可通过标志位避免重复初始化)
方法 2:使用装饰器(更灵活)
装饰器可以动态修改类的行为,通过装饰器包装类,实现单例效果。
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
| 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, db_url): self.db_url = db_url print(f"连接到数据库:{self.db_url}")
db1 = DatabaseConnection("mysql://localhost:3306/db1") db2 = DatabaseConnection("mysql://localhost:3306/db2")
print(db1 is db2) print(db1.db_url)
|
优点:
- 代码复用性好,可装饰多个类
- 实现与业务逻辑分离,符合单一职责原则
- 无需修改类的内部实现
方法 3:使用元类(最彻底的方式)
元类(Metaclass)是创建类的 “类”,控制类的创建过程。通过自定义元类,可以从根本上确保类只创建一个实例。
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 30 31
| 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 = {} print("初始化配置")
def set(self, key, value): self.settings[key] = value
def get(self, key): return self.settings.get(key)
config1 = Config() config1.set("theme", "dark")
config2 = Config() print(config1 is config2) print(config2.get("theme"))
|
优点:
- 最彻底的实现方式,从类的创建层面保证单例
- 支持继承场景(比
__new__
方法更可靠)
- 实例化过程完全受控制
方法 4:模块级单例(最简单方式)
Python 模块在首次导入时执行,后续导入会直接使用已加载的模块对象。利用这一特性,可以轻松实现单例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
class Logger: def __init__(self): self.logs = []
def log(self, message): self.logs.append(message) print(f"日志:{message}")
def get_all_logs(self): return self.logs
logger = Logger()
|
优点:
- 实现最简单,利用 Python 内置特性
- 无需额外代码,天然支持单例
- 适合简单场景(如日志工具、配置管理)
单例模式的线程安全问题
在多线程环境中,上述基础实现可能存在线程安全问题(多个线程同时判断 _instance
为 None
,导致创建多个实例)。
解决方法:添加线程锁(threading.Lock
)
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 30
| import threading
class ThreadSafeSingleton: _instance = None _lock = threading.Lock()
def __new__(cls, *args, **kwargs): if not cls._instance: with cls._lock: if not cls._instance: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance
def create_instance(): s = ThreadSafeSingleton() print(f"实例ID:{id(s)}")
threads = [threading.Thread(target=create_instance) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
|
关键点:
- 使用
threading.Lock
确保同一时间只有一个线程执行实例创建逻辑
- 双重检查锁定(Double-Checked Locking):先判断实例是否存在,再加锁,提高性能
单例模式的适用场景
- 资源共享类:
- 控制访问唯一资源:
- 状态一致性要求高的场景:
单例模式的优缺点
优点:
- 减少内存开销(只创建一个实例)
- 避免资源多重占用(如数据库连接)
- 提供全局访问点,方便状态管理
缺点:
- 违反单一职责原则(既负责业务逻辑,又负责实例管理)
- 可能隐藏依赖关系(全局访问导致代码耦合)
- 不利于单元测试(难以模拟和隔离)
- 多线程环境下需要额外处理线程安全
总结
Python 中实现单例模式的方法各有优劣,选择依据场景而定:
- 简单场景:优先使用模块级单例(最简单)
- 需要装饰多个类:使用装饰器实现
- 复杂继承场景:使用元类实现
- 多线程环境:必须添加线程锁确保安全
v1.3.10