发布日期 :2026-09-09 08:24:17 UTC
作者 :xuzhiping
访问量: 10 次浏览
单例模式(Singleton Pattern)属于创建型设计模式,目标是保证一个类在程序全局仅有唯一实例对象,并对外提供统一访问入口。
典型适用业务场景
1.数据库连接:全局复用同一个连接 / 连接池,避免频繁创建销毁连接;
2.日志记录器:防止重复打开日志文件句柄;
3.配置管理器:全局加载一份配置信息;
4.公共缓存对象:全局共享缓存实例。
利用闭包保存唯一实例,使用装饰器语法修饰目标类。
def singleton(cls):
"""单例装饰器,闭包保存唯一实例"""
_instance = None
def wrapper(*args, **kwargs):
nonlocal _instance
if _instance is None:
_instance = cls(*args, **kwargs)
return _instance
return wrapper
@singleton
class DBConnect:
def __init__(self, host):
self.host = host
print("数据库连接初始化")
# 测试
obj1 = DBConnect("127.0.0.1")
obj2 = DBConnect("192.168.1.1") # 不会再次执行初始化
print(obj1 is obj2) # True
print(obj1.host) # 127.0.0.1
特点:语法优雅,一眼就能看出该类被做成单例;缺点:容易让调用者产生误解,误以为每次调用都会新建对象。
__new__ 方法__new__ 负责对象实例的创建,重写该方法控制只生成一份实例。
class MySingleton:
"""通过__new__控制实例创建"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# 测试
obj1 = MySingleton()
obj2 = MySingleton()
print(obj1 is obj2) # True
特点:逻辑最直观;但类变量_instance对外暴露,复用性一般,多线程并发下不安全。
不修改类本身,通过工厂函数获取唯一实例。
from typing import Type, TypeVar
T = TypeVar('T')
def singleton(cls: Type[T], *args, **kwargs) -> T:
"""单例工厂函数"""
if not hasattr(cls, '_instance'):
cls._instance = cls(*args, **kwargs)
return cls._instance
class TargetClass:
def __init__(self, config):
self.config = config
# 使用,必须显式调用函数获取实例
obj = singleton(TargetClass, config="value")
特点:语义明确,使用者清楚自己在获取单例;缺点:每次获取都要调用函数,书写略繁琐。
借助 Python 内置缓存工具,缓存函数返回的对象,间接实现单例效果。
from functools import lru_cache
class MyClass:
def __init__(self, arg1):
self.arg1 = arg1
@lru_cache(maxsize=1)
def get_instance():
return MyClass(arg1="value")
# 测试
obj1 = get_instance()
obj2 = get_instance()
print(obj1 is obj2) # True
特点:标准库内置,不需要手写额外装饰器;本质是缓存,不是严格意义的单例模式,但代码极简。
核心观点:尽量不要把类本身改造为单例类;推荐写专门函数来获取全局实例,代码更透明,便于单元测试 Mock。
推荐写法:显式获取全局实例
# 写法1:原生全局变量懒加载
_db = None
def get_db():
global _db
if _db is None:
# 模拟数据库类
class Database:
pass
_db = Database()
return _db
# 写法2:搭配 lru_cache,更加简洁
from functools import lru_cache
@lru_cache
def get_db():
class Database:
pass
return Database()
不推荐:随意给类加上@singleton装饰器。除非业务 100% 确定这个类必须全局唯一,否则会带来隐性歧义,阅读代码的人不知道调用类构造器返回的永远是同一个对象。