09-单例模式
在 Python 中,单例模式是一种常用的设计模式,确保一个类只有一个实例,并提供一个全局访问点。这在需要全局共享状态、配置管理、资源管理等场景中非常有用。
核心概念
- 唯一性:类在应用程序的生命周期中只会创建一个实例。
- 全局访问:通过类提供的一个访问方法,所有使用者都能得到同一个实例。
- 延迟实例化(可选):在需要时才创建实例,减少不必要的资源占用。
单例模式的实现方式
1.使用类变量
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# 测试
a = Singleton()
b = Singleton()
print(a is b) # True
原理:
_instance
类变量保存实例。__new__
方法控制实例创建,只有在首次调用时创建实例,该方法在创建实例时自动被调用,该方法被调用后会自动调用__init__
初始化方法。
2.使用装饰器
def singleton(cls):
instances = {} # 使用字典来承接类和类实例对象的映射关系
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls] # 返回该cls类的实例对象
return get_instance
@singleton
class Singleton:
pass
a = Singleton()
b = Singleton()
print(a is b) # True
原理:
instances
字典保存类与实例的映射。- 装饰器确保只创建一个类实例。
3.使用模块(Python独有)
Python 的模块天然是单例,因为模块在第一次导入时会被缓存。
# singleton_module.py
class Singleton:
def __init__(self):
self.value = 42
singleton = Singleton()
使用时:
from singleton_module import singleton
print(singleton.value) # 42
原理:
- 模块只会被导入和执行一次,其内容被缓存。
- 无论在哪引入该模块,都会引用同一个实例。
4.使用 metaclass
元类
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 Singleton(metaclass=SingletonMeta):
pass
# 测试
a = Singleton()
b = Singleton()
print(a == b) # True
原理:
metaclass
控制类的实例化过程。__call__
拦截实例创建逻辑,确保只创建一个实例。
补充: hasattr(obj, str)
函数
在 Python 中,hasattr()
是一个内置函数,用于判断对象是否有某个属性。它的语法是:
hasattr(object, name)
object
:要检查的对象。name
:字符串,表示属性名。
如果对象中存在这个属性,hasattr()
返回 True
,否则返回 False
。
例子:
class Person:
def __init__(self, name):
self.name = name
person = Person("Alice")
print(hasattr(person, "name")) # True,因为 person 对象有 name 属性
print(hasattr(person, "age")) # False,因为没有 age 属性
print(hasattr(person, "__init__")) # True,__init__ 是类的方法,也是一个属性
注意事项:
hasattr()
实际上会尝试访问这个属性,所以如果属性的访问器中有副作用(比如抛出异常或修改状态),调用hasattr()
可能会触发这些副作用。- 如果属性存在,但访问时抛出异常,
hasattr()
会返回False
。
**小技巧:**你可以结合 getattr()
使用,在确认属性存在后再安全地获取其值:
if hasattr(person, "name"):
name = getattr(person, "name")
print(name)
5.使用 threading.Lock
保证线程安全
在多线程环境下,使用锁来保证单例安全。
import threading
class Singleton:
_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, *args, **kwargs)
return cls._instance
# 测试
a = Singleton()
b = Singleton()
print(id(a)) # 1734385437776
print(id(b)) # 1734385437776
原理:
threading.Lock()
控制对类实例的访问。- 双重检查确保指在需要时加锁,提供性能。
6.使用 @classmethod
class Singleton:
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
# 测试
a = Singleton.get_instance()
b = Singleton.get_instance()
print(a is b) # True
原理:
get_instance
是一个类方法,用于管理类的唯一实例。_instance
类变量保存唯一实例,确保只在第一次调用时创建。
优缺点:
- 优点:实现简单,逻辑清晰。
- 缺点:实例获取方式稍显繁琐,需要通过类方法调用。
- 线程安全:不安全,需要加锁处理多线程场景。
7.使用 __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):
# 这里的 __init__ 只会在首次实例化时调用
self.value = 42
# 测试
a = Singleton()
b = Singleton()
print(a is b) # True
print(a.value) # 42
b.value = 99
print(a.value) # 99 (说明是同一个实例)
原理:
__new__
是 Python 中用于控制实例创建的特殊方法,它在__init__
执行前执行。- 通过类变量
_instance
控制只创建一个实例。
优缺点:
- 优点:简单直接,兼容普通类的初始化方式。
- 缺点:没有线程安全,需要在多线程中加锁。
扩展: 加锁保证线程安全(结合 __new__
)
如果需要在多线程环境中确保单例安全,我们可以在 __new__
中加锁实现双重检查。
import threading
class Singleton:
_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(Singleton, cls).__new__(cls)
return cls._instance
# 测试
a = Singleton()
b = Singleton()
print(a is b) # True
总结这两种方法:
实现方式 | 简洁性 | 灵活性 | 线程安全 | 使用场景 |
---|---|---|---|---|
@classmethod | 中等 | 较灵活 | 否 | 明确通过类方法获取实例 |
__new__ (基础) | 简单 | 高 | 否 | 常规类初始化场景 |
__new__ (加锁) | 中等 | 高 | 是 | 多线程环境 |
这两种方法确实也是很常用的实现方式,尤其是 __new__
,可以说是 Python 中实现单例模式的经典方法。
如果你更喜欢通过类方法显式管理实例,用 @classmethod
;
如果你希望更像普通类的用法,但仍保持单例特性,用 __new__
。
你觉得哪种方式更适合你的需求?或者需要帮你完善某个实现吗? 😄
总结
实现方式 | 简洁性 | 灵活性 | 线程安全 | Python特性 |
---|---|---|---|---|
类变量 | 简单 | 高 | 否 | 否 |
装饰器 | 简洁 | 中等 | 否 | 否 |
模块 | 最简 | 低 | 是 | 是 |
元类 metaclass | 稍复杂 | 高 | 否 | 是 |
带锁的类变量 | 中等 | 高 | 是 | 否 |
如果你追求简单性,直接用模块或类变量实现;
如果需要强灵活性,metaclass
实现非常 合适;
如果是多线程场景,建议用带锁实现。
需要我帮你分析哪种实现更适合你的项目吗? 😊