(2026.03.29 Sun)
Singleton单例模式指的是一个类只有一个实例对象。本文测试环境python 3.13.5。
在Python中,最常见的单例模式是None对象。
Python中常见的实现单例模式的方法如下:
- module引用实现
- 覆盖构造函数
- 单例装饰器
- 从元类metaclass创建
此外,还有线程安全的创建方法。
module引用实现
从一个单独文件/module中引入某个类,是最简单常见的设置单例模式的方法。
# config.py
class AppConfig:
def __init__(self):
self.value_a = 1
self.value_b = 2
# main.py
from config import AppConfig
if __name__ == "__main__":
a = AppConfig()
print(a.value_a)
覆盖构造函数Override construction function __new__
在创建类时,重写构造函数__new__,检测是否已经创建实例。如果没有创建,则新创建并记录;如果已经创建,则返回已经创建的实例。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
在类Singleton中,设置一个类变量_instance。该变量因其代表了类的特性,无法被实例修改,也不能在类方法和实例方法中改动,仅仅在构造函数中检测和修改。
注意,在构造函数__new__中,传入的类本身cls而非实例本身self。
原因:在实例化过程中,首先运行构造函数
__new__,再运行初始化函数__init__。默认情况下,Python类的构造函数__new__返回当前类的一个实例对象。第一次实例化时,运行构造函数检测到类变量cls._instance为空,则调用object类的__new__方法创建一个实例对象,并赋给cls._instance。而从第二次实例化开始,因cls._instance,构造函数直接返回被cls._instance保存的实例对象本身。这个过程保证了每个实例化对象都是第一次实例化的对象。
接下来实例化并测试
a = Singleton()
b = Singleton()
a is b
>> True
两次实例化的对方分别是a和b,比较这两个对象时需要比较其内存地址,因此使用a is b语句判断而非a is b指令判断。
单例装饰器 singleton decorator
设置一个单例装饰器,其中包含一个字典,用于记录不同类的首次实例化instance。
def singleton(cls):
instances = {} # 用于保存实例
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
该装饰器中,将实例化的对象保存在instances中,并如果检测到则不再为该类创建实例。
@singleton
class ClassA:
pass
>> a = ClassA()
>> b = ClassA()
>> a is b
True
从metaclass创建
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
pass
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # Output: True
Thread-safe singleton线程安全的单例模式
在多线程环境下,上面的单例模式方法有潜在的隐患,即当多个线程同时创建一个对象时,可能导致单例类创建多个实例对象,这将失去单例模式的价值和意义。加入线程锁thread lock可解决该问题。
import threading
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
# Double-check pattern
if cls._instance is None:
print(f"Thread {threading.current_thread().name}: Creating instance")
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if not self._initialized:
with self._lock:
if not self._initialized:
print(f"Thread {threading.current_thread().name}: Initializing")
self.data = {}
self._initialized = True
Reference
- freecodecamp, Bala Priya C, How to Build a Singleton in Python (and Why You Probably Shouldn't)
- stackabuse, Scottish Robinson, Creating a Singleton in Python