# 0 . 前言
在整个进程中,有且只有一个对象存在,在任何地点使用都是同一个对象,可以解决多线程资源竞争问题,也常用于配置信息。
本文主要介绍使用python的三种实现单例模式的方式。
# 1. 在类中__new__方法中实现
在需要实现单例的 class 中添加__new__方法,在创建该 class 对象时会调用该方法,使用类变量 _instance
来保存当前对象,每次创建之前都会判断是否有该对象,没有则创建,有则直接返回。
from typing import Any
class A:
def __new__(cls, *args, **kwargs) -> Any:
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
我们创建两个 class A 对象,然后分别打印他们的内存 ID,会发现两者 ID 是一致的,也就是是同一个对象。
代码语言:javascript复制a1 = A()
a2 = A()
print(id(a1))
print(id(a2))
Output:
2659742107728
2659742107728
# 2. 通过元类实现
上面的方式需要在每一个单例类中都要添加一个__new__方法,有大量的重复代码。接下来我们介绍通过元类来实现单例。
- 第一版
首先创建 class Singleton 来继承 type,该类为我们自定义的元类。然后创建我们需要单例的 class A 和 B,它们都需要通过 metaclass=Singleton 来选择 Singleton 作为它们的元类。
在元类中,创建 __call__
方法,该方法会在 class A 和 B 创建对象时调用,在该方法中会调用 __new__
和 __init__
方法,创建完对象后,再将该对象放在类变量 _instance
中,和 1. 在__new__中实现单例 的方法一样。
from typing import Any
class Singleton(type):
def __call__(self, *args: Any, **kwds: Any) -> Any:
if not hasattr(self, "_instance"):
self._instance = super(Singleton, self).__call__(*args, **kwds)
return self._instance
class A(metaclass=Singleton):
pass
class B(metaclass=Singleton):
pass
我们再通过测试,可以看到 class A 和 B 都实现了单例。
代码语言:javascript复制a1 = A()
a2 = A()
print(id(a1))
print(id(a2))
b1 = B()
b2 = B()
print(id(b1))
print(id(b2))
Output:
>>> 2802632572784
>>> 2802632572784
>>> 2802632572400
>>> 2802632572400
但这个单例设计只适用于单线程,在多线程中,如果两个线程都停留 hasattr 下面,可能还是会创建两个对象,达不到单例的效果。
- 第二版
通过加锁解决上面并发的问题。
代码语言:javascript复制import threading
class Singleton(type):
lock = threading.Lock()
def __call__(self, *args: Any, **kwds: Any) -> Any:
with Singleton.lock:
if not hasattr(self, "_instance"):
self._instance = super(Singleton, self).__call__(*args, **kwds)
return self._instance
但是其实只有第一次创建对象时,需要通过锁同步获取单例对象,在已有对象时,不需要再用锁了,在这种情况下,每次获取对象都经过锁,会影响性能。
- 最终版
所以再加上一重判断,减少每次锁判断带来的性能消耗。
代码语言:javascript复制import threading
class Singleton(type):
lock = threading.Lock()
def __call__(self, *args: Any, **kwds: Any) -> Any:
if not hasattr(self, "_instance"):
with Singleton.lock:
if not hasattr(self, "_instance"):
self._instance = super(Singleton, self).__call__(*args, **kwds)
return self._instance
# 3. 通过装饰器实现单例
该方法是通过实现一个装饰器,在需要实现类上添加该装饰器即可完成,使用简单。
通过将所有的单例对象保存在装饰器的 _instance
字典中,以类为 key,对象为 value 进行存储。
def Singleton(cls):
_instance = {}
def _singleton(*args, **kargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return _singleton
@Singleton
class A:
pass
@Singleton
class B:
pass