Python设计模式之单例模式,使类具有唯一的实例
在Python开发中,单例模式(Singleton Pattern)可保证一个类仅有一个实例,并提供一个全局访问点。本文将全面解析单例模式的原理、实现方式及应用场景。
一、什么是单例模式?
单例模式是一种创建型设计模式,其核心目标是:
- 确保一个类在整个程序生命周期中只创建一个实例。
- 提供一个全局访问点方便获取该实例。
- 防止外部代码随意创建该类的实例。
这种模式适用于需要全局唯一的资源,如配置管理器、日志记录器、数据库连接池等。
二、单例模式的常用实现方式
1.模块级单例(Python特有)
将需要单例的类定义在一个模块中,并在模块中提前实例化,其他模块使用时可直接导入该模块的实例化对象。
案例:创建一个配置管理器的python文件,并将ConfigManager实例化为config
# config_manager.pyclass ConfigManager: def __init__(self): self.config = {} self.load_config() def load_config(self): """加载配置文件""" self.config = { "debug": False, "database": "mysql://user:pass@localhost/db", "max_connections": 10 } def get(self, key): """获取配置项""" return self.config.get(key)# 模块级实例,全局唯一config = ConfigManager()
其他python模块直接通过from config_manager import config,导入已经实例化的类对象config。
# 在其他文件中使用
from config_manager import config
print(config.get("debug")) # False
print(config.get("database")) # mysql://user:pass@localhost/db
# 验证单例性
from config_manager import config as config2
print(config is config2) # True,证明是同一个实例
优点:简单直观,利用Python特性,无需额外代码。
2.装饰器实现单例
通过装饰器包装类,控制其实例化过程,确保只创建一个实例。
装饰器函数参数:
案例1:通用单例装饰器,设置当实例化类时即使是不同的初始化参数都有且只有1个实例。
#1.创建单例装饰器函数def singleton(cls): """单例装饰器""" instances = {} # 缓存实例的字典 def wrapper(*args, **kwargs): # 如果类不在缓存中,则创建实例并缓存 if cls not in instances: instances[cls] = cls(*args, **kwargs) # 返回缓存的实例 return instances[cls] return wrapper#2.使用装饰器@singletonclass Logger: def __init__(self, log_file): self.log_file = log_file print(f"初始化日志器,日志文件:{log_file}") def log(self, message): """写入日志""" with open(self.log_file, "a") as f: f.write(f"{message}\n")#3.测试单例logger1 = Logger("app.log")logger2 = Logger("debug.log") # 注意:这里参数不同但仍返回同一个实例print(logger1 is logger2) # Trueprint(logger1.log_file) # app.log(第一次初始化的参数)print(logger2.log_file) # app.log(证明是同一个实例)
案例2:通用单例装饰器,设置不同的初始化参数返回不同的实例,但是同样的参数只有1个实例。
def singleton_with_args(cls): """支持参数的单例装饰器""" instances = {} def wrapper(*args, **kwargs): # 使用参数作为缓存键的一部分 key = (cls, args, frozenset(kwargs.items())) if key not in instances: instances[key] = cls(*args, **kwargs) return instances[key] return wrapper@singleton_with_argsclass Database: def __init__(self, host, port): self.host = host self.port = port print(f"连接数据库:{host}:{port}")# 不同参数创建不同实例db1 = Database("localhost", 3306)db2 = Database("localhost", 3306) #同样的参数返回同一个实例db3 = Database("192.168.1.1", 3306) #不同参数返回不同的实例print(db1 is db2) # Trueprint(db1 is db3) # False
3.元类实现单例(最推荐)
元类(metaclass)是类的类,控制类的创建过程。通过自定义元类,可以在类创建时确保单例特性。
核心方法:__call__ 方法,在类被实例化时调用。
案例:元类实现单例
class SingletonMeta(type): """单例元类""" _instances = {} # 存储实例的字典 def __call__(cls, *args, **kwargs): """控制类的实例化过程""" # 如果类不在实例字典中,则创建实例 if cls not in cls._instances: # 调用父类的 __call__ 方法创建实例 instance = super().__call__(*args, **kwargs) cls._instances[cls] = instance # 返回已创建的实例 return cls._instances[cls]# 使用元类class CacheManager(metaclass=SingletonMeta): def __init__(self): self.cache = {} print("初始化缓存管理器") def set(self, key, value): """设置缓存""" self.cache[key] = value def get(self, key): """获取缓存""" return self.cache.get(key)# 测试单例cache1 = CacheManager()cache2 = CacheManager()print(cache1 is cache2) # Truecache1.set("user", "admin")print(cache2.get("user")) # admin(证明共享数据)
优点:
4 使用__new__方法实现单实例
原理:__new__ 方法用于创建实例,在 __init__ 之前调用,我们可以重写 __new__ 方法控制实例创建。
案例1: 使用__new__ 方法用于创建单实例,但是每次实例时都会调用__init__方法。
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): self.value = value #注意:每次实例化仍会调用__init__# 测试s1 = Singleton(10)s2 = Singleton(20)print(s1 is s2) # Trueprint(s1.value) # 20(被第二次初始化覆盖)print(s2.value) # 20
案例2: 使用__new__ 方法用于创建单实例,防止 __init__ 被多次调用。
class Singleton: _instance = None _initialized = False # 标记是否已初始化 def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super().__new__(cls) return cls._instance def __init__(self, value): if not self._initialized: self.value = value self._initialized = True # 初始化后标记# 测试s1 = Singleton(10)s2 = Singleton(20)print(s1.value) # 10(第二次实例化后,该值未被覆盖)print(s2.value) # 10(第二次实例化后,还是使用的第1次实例化的参数值)
5 延迟初始化,直到第一次使用时才创建实例。
通过控制初始化方法,提示只能通过类方法获取实例。
class LazySingleton: _instance = None @classmethod def get_instance(cls): """获取实例的全局方法""" if not cls._instance: cls._instance = cls() return cls._instance def __init__(self): # 防止通过构造函数创建实例 if LazySingleton._instance: raise Exception("请使用 get_instance()方法获取实例") LazySingleton._instance = self# 测试s1 = LazySingleton.get_instance()s2 = LazySingleton.get_instance()print(s1 is s2) # True# 尝试直接实例化会抛出异常try: s3 = LazySingleton()except Exception as e: print(e) # 请使用get_instance()方法获取实例
三、单例模式的优缺点
优点
节省资源:避免重复创建实例,减少内存占用便于管理:集中控制资源访问,如数据库连接池缺点
隐藏依赖:单例的使用可能导致代码间的隐式依赖,降低可读性线程安全问题:多线程环境下可能创建多个实例(需要额外处理)在多线程环境下,基础的单例实现可能存在线程安全问题。以下是线程安全的实现方式:
import threadingclass ThreadSafeSingleton(metaclass=SingletonMeta): _instance = None _lock = threading.Lock() # 锁对象 @classmethod def get_instance(cls): # 双重检查锁定 if not cls._instance: with cls._lock: # 加锁保证线程安全 if not cls._instance: cls._instance = cls() return cls._instance
四、总结
单例模式是一种简单而强大的设计模式,在 Python 中有多种实现方式:
模块级单例:最简单,利用Python模块特性使用__new__ 方法:直接控制实例创建过程单例模式适用于需要全局唯一实例的场景,如配置管理、日志记录、数据库连接池等。但也要注意其潜在问题,避免过度使用导致代码耦合度升高。