失效的单例模式

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

总结
  • 单例模式不要使用基于 __new__ 的实现
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355