Python 单例模式
单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
核心概念
想象一下公司的 CEO 职位 - 整个公司只能有一个 CEO,无论哪个部门需要向 CEO 汇报,他们访问的都是同一个 CEO 对象。单例模式就是编程世界中的"CEO 职位管理机制"。
为什么需要单例模式
在实际开发中,有些对象我们只需要一个实例就足够了,比如:
- 配置管理器:整个应用程序共享同一套配置
- 数据库连接池:避免重复创建连接,节省资源
- 日志记录器:确保所有日志都写入同一个文件
- 缓存系统:统一管理缓存数据
单例模式的实现方法
Python 提供了多种实现单例模式的方式,让我们从简单到复杂逐一了解。
方法一:使用模块(最简单的方式)
Python 的模块本身就是天然的单例模式。当模块被导入时,它会被初始化一次,后续的导入都会使用同一个实例。
实例
# singleton_module.py
class DatabaseConnection:
def __init__(self):
self.connection_string = "database://localhost:5432/mydb"
print("数据库连接已创建")
def query(self, sql):
return f"执行查询: {sql}"
# 创建单例实例
db_instance = DatabaseConnection()
# 在其他文件中使用:
# from singleton_module import db_instance
# result = db_instance.query("SELECT * FROM users")
class DatabaseConnection:
def __init__(self):
self.connection_string = "database://localhost:5432/mydb"
print("数据库连接已创建")
def query(self, sql):
return f"执行查询: {sql}"
# 创建单例实例
db_instance = DatabaseConnection()
# 在其他文件中使用:
# from singleton_module import db_instance
# result = db_instance.query("SELECT * FROM users")
方法二:使用 __new__ 方法
通过重写 __new__ 方法来控制实例的创建过程。
实例
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
# 如果实例不存在,则创建新实例
if not cls._instance:
cls._instance = super().__new__(cls)
print("创建新的单例实例")
else:
print("返回已存在的单例实例")
return cls._instance
def __init__(self, name):
# 注意:__init__ 每次都会被调用
self.name = name
print(f"初始化实例,名称: {name}")
# 测试代码
print("=== 测试单例模式 ===")
s1 = Singleton("第一个实例")
s2 = Singleton("第二个实例")
print(f"s1 的 ID: {id(s1)}")
print(f"s2 的 ID: {id(s2)}")
print(f"s1 和 s2 是同一个对象吗? {s1 is s2}")
print(f"s1 名称: {s1.name}")
print(f"s2 名称: {s2.name}") # 注意:这里会显示"第二个实例"
_instance = None
def __new__(cls, *args, **kwargs):
# 如果实例不存在,则创建新实例
if not cls._instance:
cls._instance = super().__new__(cls)
print("创建新的单例实例")
else:
print("返回已存在的单例实例")
return cls._instance
def __init__(self, name):
# 注意:__init__ 每次都会被调用
self.name = name
print(f"初始化实例,名称: {name}")
# 测试代码
print("=== 测试单例模式 ===")
s1 = Singleton("第一个实例")
s2 = Singleton("第二个实例")
print(f"s1 的 ID: {id(s1)}")
print(f"s2 的 ID: {id(s2)}")
print(f"s1 和 s2 是同一个对象吗? {s1 is s2}")
print(f"s1 名称: {s1.name}")
print(f"s2 名称: {s2.name}") # 注意:这里会显示"第二个实例"
输出结果:
=== 测试单例模式 === 创建新的单例实例 初始化实例,名称: 第一个实例 返回已存在的单例实例 初始化实例,名称: 第二个实例 s1 的 ID: 140245678945600 s2 的 ID: 140245678945600 s1 和 s2 是同一个对象吗? True s1 名称: 第二个实例 s2 名称: 第二个实例
方法三:使用装饰器
创建一个通用的单例装饰器,可以轻松地将任何类转换为单例。
实例
def singleton(cls):
"""单例装饰器"""
instances = {}
def get_instance(*args, **kwargs):
# 如果该类还没有实例,则创建新实例
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
print(f"创建 {cls.__name__} 的新实例")
else:
print(f"返回已存在的 {cls.__name__} 实例")
return instances[cls]
return get_instance
@singleton
class ConfigurationManager:
def __init__(self):
self.settings = {}
self.load_default_settings()
def load_default_settings(self):
self.settings = {
"app_name": "我的应用",
"version": "1.0.0",
"debug_mode": True
}
def get_setting(self, key):
return self.settings.get(key)
def set_setting(self, key, value):
self.settings[key] = value
# 测试代码
print("\n=== 测试装饰器单例 ===")
config1 = ConfigurationManager()
config2 = ConfigurationManager()
config1.set_setting("theme", "dark")
print(f"config1 主题: {config1.get_setting('theme')}")
print(f"config2 主题: {config2.get_setting('theme')}") # 两个实例共享同一配置
"""单例装饰器"""
instances = {}
def get_instance(*args, **kwargs):
# 如果该类还没有实例,则创建新实例
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
print(f"创建 {cls.__name__} 的新实例")
else:
print(f"返回已存在的 {cls.__name__} 实例")
return instances[cls]
return get_instance
@singleton
class ConfigurationManager:
def __init__(self):
self.settings = {}
self.load_default_settings()
def load_default_settings(self):
self.settings = {
"app_name": "我的应用",
"version": "1.0.0",
"debug_mode": True
}
def get_setting(self, key):
return self.settings.get(key)
def set_setting(self, key, value):
self.settings[key] = value
# 测试代码
print("\n=== 测试装饰器单例 ===")
config1 = ConfigurationManager()
config2 = ConfigurationManager()
config1.set_setting("theme", "dark")
print(f"config1 主题: {config1.get_setting('theme')}")
print(f"config2 主题: {config2.get_setting('theme')}") # 两个实例共享同一配置
方法四:使用元类(高级用法)
元类可以控制类的创建过程,是实现单例模式的另一种强大方式。
实例
class SingletonMeta(type):
"""单例元类"""
_instances = {}
def __call__(cls, *args, **kwargs):
# 如果该类还没有实例,则创建新实例
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
print(f"元类:创建 {cls.__name__} 的新实例")
else:
print(f"元类:返回已存在的 {cls.__name__} 实例")
return cls._instances[cls]
class Logger(metaclass=SingletonMeta):
def __init__(self, log_file="app.log"):
self.log_file = log_file
self.logs = []
print(f"日志器初始化,文件: {log_file}")
def log(self, message):
log_entry = f"[{self.get_timestamp()}] {message}"
self.logs.append(log_entry)
print(f"记录日志: {log_entry}")
return log_entry
def get_timestamp(self):
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def get_logs(self):
return self.logs.copy()
# 测试代码
print("\n=== 测试元类单例 ===")
logger1 = Logger("application.log")
logger2 = Logger("different.log") # 这个文件名会被忽略
logger1.log("系统启动")
logger2.log("用户登录")
print(f"logger1 日志数量: {len(logger1.get_logs())}")
print(f"logger2 日志数量: {len(logger2.get_logs())}")
print(f"是同一个日志器吗? {logger1 is logger2}")
"""单例元类"""
_instances = {}
def __call__(cls, *args, **kwargs):
# 如果该类还没有实例,则创建新实例
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
print(f"元类:创建 {cls.__name__} 的新实例")
else:
print(f"元类:返回已存在的 {cls.__name__} 实例")
return cls._instances[cls]
class Logger(metaclass=SingletonMeta):
def __init__(self, log_file="app.log"):
self.log_file = log_file
self.logs = []
print(f"日志器初始化,文件: {log_file}")
def log(self, message):
log_entry = f"[{self.get_timestamp()}] {message}"
self.logs.append(log_entry)
print(f"记录日志: {log_entry}")
return log_entry
def get_timestamp(self):
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def get_logs(self):
return self.logs.copy()
# 测试代码
print("\n=== 测试元类单例 ===")
logger1 = Logger("application.log")
logger2 = Logger("different.log") # 这个文件名会被忽略
logger1.log("系统启动")
logger2.log("用户登录")
print(f"logger1 日志数量: {len(logger1.get_logs())}")
print(f"logger2 日志数量: {len(logger2.get_logs())}")
print(f"是同一个日志器吗? {logger1 is logger2}")
单例模式的应用场景
让我们通过一个完整的示例来看看单例模式在实际项目中的应用。
实战案例:应用配置管理器
实例
class AppConfig:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
# 防止重复初始化
if not self._initialized:
self.config_data = {}
self.load_config()
self._initialized = True
def load_config(self):
"""模拟从配置文件加载配置"""
self.config_data = {
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp_db"
},
"server": {
"host": "0.0.0.0",
"port": 8000
},
"features": {
"cache_enabled": True,
"debug_mode": False
}
}
print("配置加载完成")
def get(self, key_path, default=None):
"""通过路径获取配置值,如 'database.host'"""
keys = key_path.split('.')
value = self.config_data
try:
for key in keys:
value = value[key]
return value
except (KeyError, TypeError):
return default
def set(self, key_path, value):
"""设置配置值"""
keys = key_path.split('.')
config = self.config_data
# 遍历到最后一个键的前一个
for key in keys[:-1]:
if key not in config:
config[key] = {}
config = config[key]
# 设置最终的值
config[keys[-1]] = value
print(f"配置已更新: {key_path} = {value}")
# 使用示例
def demonstrate_config_usage():
print("\n=== 配置管理器使用演示 ===")
# 在不同地方获取配置管理器实例
config1 = AppConfig()
config2 = AppConfig()
print(f"是同一个配置管理器吗? {config1 is config2}")
# 读取配置
db_host = config1.get("database.host")
server_port = config1.get("server.port")
print(f"数据库主机: {db_host}")
print(f"服务器端口: {server_port}")
# 更新配置
config2.set("database.host", "192.168.1.100")
config2.set("features.debug_mode", True)
# 验证配置同步
print(f"config1 数据库主机: {config1.get('database.host')}")
print(f"config1 调试模式: {config1.get('features.debug_mode')}")
# 运行演示
demonstrate_config_usage()
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
# 防止重复初始化
if not self._initialized:
self.config_data = {}
self.load_config()
self._initialized = True
def load_config(self):
"""模拟从配置文件加载配置"""
self.config_data = {
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp_db"
},
"server": {
"host": "0.0.0.0",
"port": 8000
},
"features": {
"cache_enabled": True,
"debug_mode": False
}
}
print("配置加载完成")
def get(self, key_path, default=None):
"""通过路径获取配置值,如 'database.host'"""
keys = key_path.split('.')
value = self.config_data
try:
for key in keys:
value = value[key]
return value
except (KeyError, TypeError):
return default
def set(self, key_path, value):
"""设置配置值"""
keys = key_path.split('.')
config = self.config_data
# 遍历到最后一个键的前一个
for key in keys[:-1]:
if key not in config:
config[key] = {}
config = config[key]
# 设置最终的值
config[keys[-1]] = value
print(f"配置已更新: {key_path} = {value}")
# 使用示例
def demonstrate_config_usage():
print("\n=== 配置管理器使用演示 ===")
# 在不同地方获取配置管理器实例
config1 = AppConfig()
config2 = AppConfig()
print(f"是同一个配置管理器吗? {config1 is config2}")
# 读取配置
db_host = config1.get("database.host")
server_port = config1.get("server.port")
print(f"数据库主机: {db_host}")
print(f"服务器端口: {server_port}")
# 更新配置
config2.set("database.host", "192.168.1.100")
config2.set("features.debug_mode", True)
# 验证配置同步
print(f"config1 数据库主机: {config1.get('database.host')}")
print(f"config1 调试模式: {config1.get('features.debug_mode')}")
# 运行演示
demonstrate_config_usage()
单例模式的注意事项
优点
- 资源节约:避免重复创建对象,节省内存和系统资源
- 数据一致性:所有客户端使用同一个实例,确保数据一致
- 全局访问:提供统一的访问点,便于管理
缺点
- 全局状态:单例对象持有全局状态,可能引起意外的副作用
- 测试困难:由于全局状态,单元测试可能变得复杂
- 违反单一职责:单例类既要管理自己的业务逻辑,又要控制实例化
最佳实践
实例
class ThreadSafeSingleton:
"""线程安全的单例模式"""
_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)
print("创建线程安全的单例实例")
return cls._instance
def __init__(self):
# 确保只初始化一次
if not hasattr(self, '_initialized'):
self.data = {}
self._initialized = True
"""线程安全的单例模式"""
_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)
print("创建线程安全的单例实例")
return cls._instance
def __init__(self):
# 确保只初始化一次
if not hasattr(self, '_initialized'):
self.data = {}
self._initialized = True
练习与思考
动手练习
实现一个缓存管理器:
- 创建一个单例的缓存类
- 支持设置、获取、删除缓存项
- 添加缓存过期时间功能
改进配置管理器:
- 添加从 JSON 文件加载配置的功能
- 实现配置变更监听器
- 添加配置验证机制
思考题
- 在什么情况下应该避免使用单例模式?
- 单例模式如何影响代码的可测试性?
- 在多线程环境中使用单例模式需要注意什么?
总结
单例模式是 Python 中非常重要的设计模式,它通过确保一个类只有一个实例,提供了对资源的统一管理。通过模块、__new__ 方法、装饰器和元类等多种实现方式,我们可以根据具体需求选择最适合的方法。
记住,虽然单例模式很实用,但也要谨慎使用,避免过度使用导致的代码耦合和测试困难。在实际项目中,合理运用单例模式可以大大提升代码的质量和可维护性。
点我分享笔记