Python 单例模式

Python 单例模式

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")

方法二:使用 __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}") # 注意:这里会显示"第二个实例"

输出结果:

=== 测试单例模式 ===

创建新的单例实例

初始化实例,名称: 第一个实例

返回已存在的单例实例

初始化实例,名称: 第二个实例

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')}") # 两个实例共享同一配置

方法四:使用元类(高级用法)

元类可以控制类的创建过程,是实现单例模式的另一种强大方式。

实例

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}")

单例模式的应用场景

让我们通过一个完整的示例来看看单例模式在实际项目中的应用。

实战案例:应用配置管理器

实例

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()

单例模式的注意事项

优点

资源节约:避免重复创建对象,节省内存和系统资源

数据一致性:所有客户端使用同一个实例,确保数据一致

全局访问:提供统一的访问点,便于管理

缺点

全局状态:单例对象持有全局状态,可能引起意外的副作用

测试困难:由于全局状态,单元测试可能变得复杂

违反单一职责:单例类既要管理自己的业务逻辑,又要控制实例化

最佳实践

实例

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

练习与思考

动手练习

实现一个缓存管理器:

创建一个单例的缓存类

支持设置、获取、删除缓存项

添加缓存过期时间功能

改进配置管理器:

添加从 JSON 文件加载配置的功能

实现配置变更监听器

添加配置验证机制

思考题

在什么情况下应该避免使用单例模式?

单例模式如何影响代码的可测试性?

在多线程环境中使用单例模式需要注意什么?

总结

单例模式是 Python 中非常重要的设计模式,它通过确保一个类只有一个实例,提供了对资源的统一管理。通过模块、__new__ 方法、装饰器和元类等多种实现方式,我们可以根据具体需求选择最适合的方法。

记住,虽然单例模式很实用,但也要谨慎使用,避免过度使用导致的代码耦合和测试困难。在实际项目中,合理运用单例模式可以大大提升代码的质量和可维护性。

相关推荐

《想不想修真》地仙境界攻略
365老玩家入口

《想不想修真》地仙境界攻略

10-31 435
铑价三连涨!南非电力危机致铑矿减产25%,汽车产业升级如何点燃价格狂潮?
再刷一把2金色传说通关攻略大全 第1-20层全通关攻略总汇