Python3 单例模式:确保类仅有一个实例
Python 单例模式:一个类只能有一个实例,到底怎么实现?
写程序的时候,有些东西全局只能有一份——配置管理器、日志记录器、数据库连接池。如果每个地方都 new 一个,资源浪费不说,状态还不一致。
单例模式就是干这个的:确保一个类在整个程序里只有一个实例,所有人都用它。
单例要解决的问题,先看一个反例
class Config:
def __init__(self):
self.settings = {}
config1 = Config()
config1.settings["theme"] = "dark"
config2 = Config()
print(config2.settings) # {},白存了
config1 和 config2 是两个不同的对象,配置存到 config1 里,config2 拿不到。这就是问题——配置这种全局只有一份的东西,不应该被重复创建。
单例模式要的效果:
config1 = Config()
config2 = Config()
print(config1 is config2) # True,同一个对象
最简单:模块级单例,利用 Python 的导入机制
Python 的模块在第一次被导入时执行,之后再导入直接用已经加载好的。这是最简单、最 Pythonic 的方式。
config.py:
class Config:
def __init__(self):
self.settings = {}
def set(self, key, value):
self.settings[key] = value
def get(self, key):
return self.settings.get(key)
# 直接在模块里创建一个实例
config = Config()
使用:
from config import config
config.set("theme", "dark")
# 任何其他地方 from config import config 拿到的都是同一个对象
优点: 代码量最少,不需要任何技巧,Python 天然支持。
缺点: 不能延迟初始化(导入的时候实例就创建了),灵活性稍差。
适合场景: 日志记录器、配置管理、工具类等简单场景。大部分时候这就够了,不需要更复杂的实现。
_new_方式:控制实例的创建过程
如果你确实需要”调用类的构造函数来控制单例”,用 __new__。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
# 注意:__init__ 每次都会被调用,需要加标志避免重复初始化
if not hasattr(self, "_initialized"):
self.value = value
self._initialized = True
s1 = Singleton("first")
s2 = Singleton("second")
s1 is s2 # True
s1.value # "first",第二次初始化没生效
关键点:
__new__在__init__之前调用,负责”造出对象”__new__返回什么,__init__就初始化什么- 判断
_instance is None决定是否创建,否则返回已有对象 - 但
__init__每次都会被调用,需要用hasattr或_initialized标志阻止重复初始化
适合场景: 类需要正常的构造函数调用方式(MyClass()),不想用模块级变量。
装饰器方式:不改类代码,加个装饰器就行
如果你不想修改类的内部实现,可以加个装饰器。
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
class DatabaseConnection:
def __init__(self, url):
self.url = url
print(f"连接:{url}")
db1 = DatabaseConnection("mysql://localhost")
db2 = DatabaseConnection("mysql://localhost") # 不会新建
db1 is db2 # True
优点: 不侵入类的内部代码,可以给多个类复用。
缺点: 装饰器包装后,类的类型信息可能会变(type(db1) 不再是 DatabaseConnection),需要额外处理。
适合场景: 你想给现有的类加单例行为,但不想改它的代码。
元类方式:从根源上控制类的行为
元类是”创建类的类”,控制类的实例化过程。用元类实现单例是最彻底的。
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 = {}
c1 = Config()
c2 = Config()
c1 is c2 # True
原理: 类的 __call__ 方法控制着 MyClass() 这个调用的行为。元类重写了 __call__,在每次”创建实例”时判断是否已经存在。
优点: 最彻底,支持继承(子类也会自动单例),行为最接近”类本身保证单例”。
缺点: 元类对初学者不太友好,理解成本高。
适合场景: 框架级别的基础类,或者你需要确保所有子类也都是单例的时候。
线程安全问题
上面的 __new__ 和元类实现,在多线程环境下可能出问题——两个线程同时判断 _instance is None,都认为该创建,结果造出两个实例。
加锁解决:
import threading
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
# 第一重检查(无锁,快速路径)
if cls._instance is None:
with cls._lock:
# 第二重检查(有锁,确保只创建一次)
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
这叫”双重检查锁”,常见于多线程单例实现。
但要注意: 如果你的应用不是多线程的,不需要加锁,徒增开销。
一个完整的例子:数据库连接池
import threading
class DatabaseConnection:
_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)
return cls._instance
def __init__(self):
if not hasattr(self, "_initialized"):
self.connections = []
self._initialized = True
def get_connection(self):
print("获取一个数据库连接")
return "connection"
def release_connection(self, conn):
print("释放连接")
# 使用
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True