失效的单例模式

2020-09-16  本文已影响0人  Yucz

简书上为首发,拒绝抄袭,原文地址 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']

输出的确是正常的,我们可以看出 ab 以及 c 都是同一对象。并且我们后续分别往 abc 中的 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']

这时我们就发现问题了!

abc 还是同一个对象,但是我们往 ab 中塞进的数据竟然不见了,最终的 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) 时,发现 abtest_list 的 id 竟然变成和 c 一致,即 ab 中的 test_list 被覆盖了,原先的各自的内容都丢失了!

这明显不符合单例模式的应用场景! 单例模式的这种实现有 BUG!

总结
上一篇下一篇

猜你喜欢

热点阅读