python 单例模式

在软件工程中,设计模式是针对软件设计中普遍存在(反复出现)的各种问题所提出的解决方案。设计模式能使不稳定依赖于相对稳定,使具体依赖于相对抽象,避免会引起麻烦的紧耦合,可以增强软件设计面对并适应变化的能力。简单来说设计模式的目的是对软件代码解耦和提高代码的适应能力。

嵌套类

首先是最容易理解的一种实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Singleton:

# 实际上的实例与类
# 注意是类变量, 所以使用时要加类名
_instance = None
class _A:
def __init__(self):
pass
def getid(self):
return id(self)

# 第一次时生成
def __init__(self):
if not Singleton._instance:
Singleton._instance = Singleton._A()

# 转交给 _instance 处理
def __getattr__(self, attr):
return getattr(Singleton._instance, attr)

if __name__ == "__main__":
s1 = Singleton()
s2 = Singleton()
print(s1, s1.getid())
print(s2, s2.getid())

输出:

1
2
<__main__.Singleton object at 0x000001A321F9F0F0> 1800161325632
<__main__.Singleton object at 0x000001A321F9F278> 1800161325632

可以看到尽管不是一个 Singleton 实例, 但之中的 _A 是一致的, 但这样的类嵌套并不推荐.

类装饰器

我们可以使用一个类装饰器来实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Singleton:
def __init__(self, cls):
# 保存类变量
self._cls = cls

def __instancecheck__(self, inst):
# 重写 isinstance
return isinstance(inst, self._decorated)

def __call__(self):
# 不可直接使用
raise TypeError('pls use {}.Instance() to get singleton'.format(self._cls.__name__))

def Instance(self):
# 此处也可用 hasattr
try:
return self._instance
except AttributeError:
self._instance = self._cls()
return self._instance

@Singleton
class A:
def __init__(self):
print('at A()')
def getid(self):
return id(self)


if __name__ == "__main__":
a1 = A.Instance()
a2 = A.Instance()
print(a1, id(a1), a1.getid())
print(a2, id(a2), a2.getid())
print(a1 is a2)

输出为:

1
2
3
4
at A()
<__main__.A object at 0x000001FD6646F390> 2187854279568 2187854279568
<__main__.A object at 0x000001FD6646F390> 2187854279568 2187854279568
True

但是这种方式并不支持多线程中保持单例, 当 __init__ 较为费时的时候就会出现不一致的情况

类装饰器 + 加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import threading

class Singleton:
# 锁
_lock = threading.Lock()
def __init__(self, cls):
# ...
def Instance(self):
# 用锁
with Singleton._lock:
try:
return self._instance
except AttributeError:
self._instance = self._cls()
return self._instance

# ...

def job(i):
a = A.Instance()
print(i, id(a), a.getid())

if __name__ == "__main__":
for i in range(10):
threading.Thread(target=job, args=[i,]).start()

输出为:

1
2
3
4
5
6
7
8
9
10
11
at A()
0 <__main__.A object at 0x000002C673E34668> 3051371054696
1 <__main__.A object at 0x000002C673E34668> 3051371054696
3 <__main__.A object at 0x000002C673E34668> 3051371054696
5 <__main__.A object at 0x000002C673E34668> 3051371054696
7 <__main__.A object at 0x000002C673E34668> 3051371054696
9 <__main__.A object at 0x000002C673E34668> 3051371054696
4 <__main__.A object at 0x000002C673E34668> 3051371054696
8 <__main__.A object at 0x000002C673E34668> 3051371054696
6 <__main__.A object at 0x000002C673E34668> 3051371054696
2 <__main__.A object at 0x000002C673E34668> 3051371054696

使用 __new__ 方法

以上都需要使用类的静态方法, 对 python 来说, 还有更优雅的解决方法, 那就是使用 __new__ 方法.

__new____init__ 的区别是什么呢, 简单来讲在生成一个类的实例时, 由 __new__ 生成实例,
再用 __init__ 初始化, 后者调用前者.
所以实现单例可以重写 __new__ 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import threading
import time

# 只有类 A
class A:
_lock = threading.Lock()

def __init__(self):
print('at __init__')
time.sleep(1)

def __new__(cls, *args, **kwargs):
with A._lock:
print('at __new__')
try:
return A._instance
except AttributeError:
# 此处只有一次
print('at creation')
A._instance = object.__new__(cls, *args, **kwargs)
return A._instance

def job(i):
a = A()
print(i, a, id(a))

if __name__ == "__main__":
for i in range(10):
threading.Thread(target=job, args=[i,]).start()

输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
at __new__
at creation
at __init__
at __new__
at __init__
at __new__
at __init__
at __new__
at __init__
at __new__
at __init__
at __new__
at __init__
at __new__
at __init__
at __new__
at __init__
at __new__
at __init__
at __new__
at __init__
9 <__main__.A object at 0x0000023F64F21C88> 2471299783816
8 <__main__.A object at 0x0000023F64F21C88> 2471299783816
5 <__main__.A object at 0x0000023F64F21C88> 2471299783816
4 <__main__.A object at 0x0000023F64F21C88> 2471299783816
2 <__main__.A object at 0x0000023F64F21C88> 2471299783816
0 <__main__.A object at 0x0000023F64F21C88> 2471299783816
6 <__main__.A object at 0x0000023F64F21C88> 2471299783816
3 <__main__.A object at 0x0000023F64F21C88> 2471299783816
7 <__main__.A object at 0x0000023F64F21C88> 2471299783816
1 <__main__.A object at 0x0000023F64F21C88> 2471299783816

ref: https://www.cnblogs.com/huchong/p/8244279.html
ref: https://juejin.im/post/5add4446f265da0b8d4186af