简书上为首发,拒绝抄袭,原文地址 https://www.jianshu.com/p/a7f1e58f5ee6
失效的单例模式
Python的单例模式实现方法有很多种,其中有一种是基于 __new__
和让子类去继承的实现方法。但是根据我的踩坑经验,这种方法往往是有问题的。试了下网上的多种实现方法,都有问题! 这种实现的单例并不是真正的单例! 这种实现在单线程中都存在问题,更别说在复杂的多线程环境中了
代码实现
我们就拿 stackoverflow
的 高赞回答作分析(网上的其他回答都大同小异),其中所提到的 基于 __new__
的实现代码如下
class Singleton(object):
_instances = {}
def __new__(class_, *args, **kwargs):
if class_ not in class_._instances:
class_._instances[class_] = \
super(Singleton, class_).__new__(class_, *args, **kwargs)
return class_._instances[class_]
class MyClass(Singleton):
pass
c = MyClass()
实现的原理在于,控制子类的 __new__
方法,让其返回的都是同一个对象
初步测试
我们不妨写一些测试代码去分析
class A(Singleton):
def __init__(self):
self.test_list = []
a = A()
b = A()
c = A()
a.test_list.append('a')
b.test_list.append('b')
c.test_list.append('c')
print(id(a), id(b), id(c))
print(a is b and b is c)
print("a.test_list =", a.test_list)
print("b.test_list =", b.test_list)
print("c.test_list =", c.test_list)
输出
140072986470664 140072986470664 140072986470664
True
a.test_list = ['a', 'b', 'c']
b.test_list = ['a', 'b', 'c']
c.test_list = ['a', 'b', 'c']
输出的确是正常的,我们可以看出 a
和 b
以及 c
都是同一对象。并且我们后续分别往 a
和 b
和 c
中的 test_list
都存入数据后,每个实例的 test_list
都是 ['a', 'b', 'c']
, 这就是我们想要的结果!
但这是否意味着这个单例的实现没问题呢?我们再做如下实验
进一步测试
我们把对每个实例的 test_list
插入顺序,改成实例化完,立马插入
看下结果会怎样,测试代码如下
class A(Singleton):
def __init__(self):
self.test_list = []
a = A()
a.test_list.append('a')
b = A()
b.test_list.append('b')
c = A()
c.test_list.append('c')
print(id(a), id(b), id(c))
print(a is b and b is c)
print("a.test_list =", a.test_list)
print("b.test_list =", b.test_list)
print("c.test_list =", c.test_list)
输出
140373892725000 140373892725000 140373892725000
True
a.test_list = ['c']
b.test_list = ['c']
c.test_list = ['c']
这时我们就发现问题了!
a
和 b
和 c
还是同一个对象,但是我们往 a
和 b
中塞进的数据竟然不见了,最终的 test_list
只有 c
! 单例失效了! 这个单例的实现有问题!
原因分析
这个问题出现的原因就在于使用 __new__
实现的 Singlegon
,只会控制子类的 __new__
实现,而不会控制子类的 __init__
的实现
当我们的子类自实现 __new__
方法时,每一次实例化子类,虽然子类的 __new__
确保最终只会实例化一次,但是 __new__
执行完后,子类还会执行自己的 __init__
方法,当__init__
中存在属性时,属性就会被多次声明,最终新生成的属性会覆盖掉上一次生成的属性!
我们可以针对上述的代码加一些打印进行跟踪
代码如下
class A(Singleton):
def __init__(self):
self.test_list = []
print("id(test_list) =", id(self.test_list))
a = A()
a.test_list.append('a')
b = A()
b.test_list.append('b')
c = A()
c.test_list.append('c')
print("id(a.test_list) =", id(a.test_list))
print("id(b.test_list) =", id(a.test_list))
print("id(c.test_list) =", id(c.test_list))
print("a.test_list =", a.test_list)
print("b.test_list =", b.test_list)
print("c.test_list =", c.test_list)
输出
id(test_list) = 139866765387336 # a 的 test_list
id(test_list) = 139866765389512 # b 的 test_list
id(test_list) = 139866765387336 # c 的 test_list
id(a.test_list) = 139866765387336 # 打印的是 c 的 test_list
id(b.test_list) = 139866765387336 # 打印的是 c 的 test_list
id(c.test_list) = 139866765387336 # 打印的是 c 的 test_list
a.test_list = ['c']
b.test_list = ['c']
c.test_list = ['c']
我们可以看出,在 __init__
中打印 id(self.test_list)
时,执行了三次,说明 __init__
方法执行了三次
并且 每个实例的 id(test_list)
是不一样的,可以看出 self.test_list
确实被定义了三次
而在 a b c
实例化完成后,我们再去打印每个实例的 id(test_list)
时,发现 a
和 b
的 test_list
的 id 竟然变成和 c
一致,即 a
和 b
中的 test_list
被覆盖了,原先的各自的内容都丢失了!
这明显不符合单例模式的应用场景! 单例模式的这种实现有 BUG!
总结
- 单例模式不要使用基于
__new__
的实现
网友评论