Python单例模式的设计与实现【完美版】

@[toc]

1. 按

众所周知,对象是解决继承的问题的,如果没有对象,那么将很难解决继承的问题。这点儿和人类的现实世界有相似之处,没有对象的话何谈子嗣的继承问题?
没有对象,将意味着很难实现继承时的多态、很难去塑造子类,没有子类又将意味着父类在设计时要具备所有的功能,这绝对是极不现实的。一种很现实的设计是:父类先解决一些问题,然后子类再解决一些问题,接着子类的子类再解决一些问题,这样子子孙孙无穷尽也,一定能够把所有问题都解决好的,这种设计模式也一定能够应对复杂多变的现实环境,因此对象的存在意义重大。
但对象是越多越好吗?答案绝对是否定的,很多情况下我们只需要一个对象就可以了,多余的对象会带来很多的不必要的开销和麻烦。这是因为每个对象都要占据一定的内存空间和CPU算力,大多数情况下我们只需要一个对象去执行任务,对于一颗CPU的核心而言,操作一个对象是最快的,为什么?这主要是由于线程的切换会造成不必要的路程开销。
设想一下,假如你有一个对象可以用来接吻,你的DNA序列约定你一生要完成4800次接吻才算完成了任务。这样在你临死的时候才会死而无憾,即程序完成所有的任务后正常退出,返回值为0,代表剩余待做的任务数为0。平均你和一个对象每天的最多能接吻48次,这样只和单个对象全力接吻的话100天就能完成任务。
但如果你有多个对象的话,你的平均作战能力并不会提升,这是因为每天接吻48次是你的处理极限,相反,你因为要和多个对象进行接吻,每次从一个对象移动到另一个对象那里会产生不必要的路程开销,会大大影响你的工作效率。
假设你有两个对象,对象A在河南,对象B在河北,你从河南到河北需要一天的时间,你每天工作完了之后会切换到另一个对象那里。假设先从与对象A接吻开始,工作一天共接吻48次,但第二天你需要移动到对象B那里,我们知道,移动的过程中是没有对象可以接吻的,只有到达目的地找到人之后才能进入工作状态。
你切换任务的过程将消耗一天的时间,这一天等于白白浪费了。因为你今天并没有执行有意义的接吻工作,虽然你也在马不停蹄地忙碌,但那用于移动距离、寻找目标的忙碌对完成接吻任务而言是不必要的,也是没有意义的。这样的话,你是一天干活,一天用于切换任务,即从当前对象跑到另一个对象那里,平均下来,两天只能完成48次的接吻任务,这样的话你需要花费200天才能完成DNA上面约定的接吻任务量,效率比着单对象模式大打折扣。
这里只考虑完工作一天之后才切换目标的情况,假如你没有太多耐心,打一枪立马就换地方,即完成一次接吻之后立即就跑到另一个对象那里。这样的话你将消耗4900天的时间才能完成任务,效率极低,需要13年多才能完成任务。
再假设你有多个对象,你的耐心很少,打一枪换十个地方,即完成一次接吻之后,立马跑到下一个对象那里,但发现这个对象没化妆、没打扮,很难看,然后再立马跑到下一个对象那里,平均见十个对象才碰到一个满意的,这样的话你将消耗48100天才能完成任务,效率更低了,需要131多年才能完成任务,怕是你这辈子不能死而无憾了,即返回值不能为0了。
在假设你根本没有耐心,打一枪换n个地方,即完成了一次接吻之后,立马跑到下一个对象那里,但发现这个对象没化妆、没打扮,很难看,然后再立马跑到下一个对象那里,接着就不断地在重复这种死循环的奔波状态,因为毫无耐心,眼光又很高、很挑剔,这辈子都在寻找合适的对象用于接吻,但一直都找不到。这种现象在计算机的状态切换中被称为死锁,死锁是存在的,死锁一旦出现,程序就死了,不会再执行任务了,陷入了一直切换状态的情形中。这样的话程序不论跑多久都不能完成任务,强制退出或者意外中断,返回值都不可能为0。

在计算机中,类的对象又称为类的实例,因此我们把一个类只生成一个对象的模式称为单例模式

以下是常见的几种创建单例的模式。
说明:我写的懒汉式与饿汉式和别人的命名刚好是相反的,这个感觉每个人的理解不同,叫什么名字无所谓啦,只要能理解思想就行。

2. 本文地址

  1. 博客园:https://www.cnblogs.com/coco56/p/11253656.html
  2. 简书:https://www.jianshu.com/p/4c47f8e3809b
  3. CSDN:https://blog.csdn.net/COCO56/article/details/97409050

3. 通过继承单例父类来实现

实测发现通过继承的方法来实现Python程序的单例模式设计是最完美的,之前尝试过使用装饰器来实现单例模式,但具体实践的时候会出现诸多问题。
比如如果在装饰器中添加了getInstance方法和Instance属性,那主流的IDE或者编辑器将无法推导出来有这个方法或属性,尽管你的语法是正确的,但由于编辑器无法推导出来,你将在编写代码的时候无法使用tab键快速补全,很不方便。
自身是为了自身的存在而存在的,用自身的存在去完善和改良自身的存在无疑是最好的选择。
前面介绍过,对象的存在其实是为了更好地解决继承的问题的。既然是解决继承的问题,那么无疑最好的选择就是用继承本身去解决继承。
具体做法为:建一个单例父类,这个类只解决单例模式的问题,然后所有需要使用单例模式的类全部继承自这个单例父类。
相当于老祖先把单例模式的问题解决了,孩子们只需要继承老祖先的基因就可以了,这样,孩子们天生就是单例模式(因为老祖先除了解决单例问题,其他什么问题都不去做,相当于穷尽毕生的力量将单例问题研究地透透的,孩子们因为继承自老祖先,本身肯定已经保留了老祖先的优良基因)。
经实际测试,这种用继承本身去解决继承相关问题的方法是完美的。
示例代码:

# -*- coding : utf-8 -*-
import threading

_instances = {}#相当于民政局的登记簿,用于记录每个单例类的专属对象
_lock = threading.Lock()#使用线程锁以确保线程安全

class SingletonClass(object):
    global _instances, _lock

    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of', cls, *args, **kwargs)
        if cls in _instances: return _instances[cls]
        _lock.acquire()#上锁
        _instances[cls] = cls._instances = super().__new__(cls)
        _lock.release()#解锁
        return _instances[cls]


    @classmethod #用于删除对象,此方法仅在必要时使用,因为如果反复的析构和构造对象的话是极其浪费资源的。
    def delInstance(cls, *args, **kwargs):
        if cls in _instances: _instances[cls].__del__(*args, **kwargs); del _instances[cls]

    @classmethod #用于获取对象
    def getInstance(cls, *args, **kwargs):
        if cls in _instances: return _instances[cls]
        return cls(*args, **kwargs)

class CopyTree(SingletonClass):
    def __init__(self, *args, **kwargs): print('__init__ of', self, *args, **kwargs)

    def __del__(self, *args, **kwargs): print('__del__ of', self, *args, **kwargs)

class CopySubTree(CopyTree):
    pass

class CopyBCSubTree(CopyTree):
    pass

if __name__ == "__main__":
    #调用构造函数和调用getInstance方法虽然都是获得的单例。但对于已存在的对象来说:调用构造函数,会重新调用__init__方法再初始化一遍儿此对象。

    cls =CopyTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
    c = cls.getInstance(); d = cls.getInstance();  print('c=', c, 'd=', d, '\n')

    cls =CopySubTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
    c = cls.getInstance(); d = cls.getInstance();  print('c=', c, 'd=', d, '\n')

    cls =CopyBCSubTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
    c = cls.getInstance(); d = cls.getInstance();  print('c=', c, 'd=', d, '\n')

    print('\nbefore del:\n', _instances); cls.delInstance(); print('after del:\n', _instances);

4. 使用装饰器实现

4.1. 懒汉式

懒汉式就是说“国家”分配对象,在你还未出生的时候就已经被指腹为婚了,这样在你出生的时候就立即拥有了一个对象了,再也不用发愁对象的事儿了,这种不需要自己主动付出就能得到对象的模式被称为懒汉模式。
为了方便,这里我是用装饰器,对需要的类进行装饰,达到了一次定义,以后再处处使用时只需要一行代码的效果。
优点:省心,开始的时候一人分一个对象就好了,很省事。
缺点:构造函数只能是无参的,自己有想法,想传个参数的话是不好传过去的。这是因为在设计时就没考虑你有想法的情况下,别管三七二十一,上来就给你分一个对象。

def Singleton(cls):
    print("正在进行装饰类", cls, '哦~')
    cls._instance = cls()#需要传参数的话在这里改一下参数列表,但那样的话就不具备如此广泛的通用性了,我们想要的效果是一次定义装饰器,以后对所有的类都适用。
    print('类', cls, '装饰完毕,并且我们还给它分配了一个对象', cls._instance, '\n')

    def _singleton(): return cls._instance
    return _singleton

@Singleton
class A(object):
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of ', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('__init__ of', self)
    

@Singleton
class B(object):
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of ', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('__init__ of', self)


if __name__ == '__main__':
    print()

    a1 = A(); a2 = A(); print(a1, a2)
    print()

    b1 = B(); b2 = B(); print(b1, b2)

4.2. 饿汉式

饿汉式是指你饿了才给你分一个对象,把对象只分配给那些饥渴难耐大汉们(不分的话可能会出现问题,毕竟大汉如果发起情来还是很骚的,恐怕难以招架)。
优点:节省空间,物尽其用,需要时才给你,如果不需要想单身一辈子的话就不给你分配对象了。
缺点:可能存在线程不安全,饥渴难耐的大汉如果在分配对象的时候一下子多占了多个不同的对象怎么办?

4.2.1. 未加锁版

一般在初始化对象的时候如果不进行IO操作,是没事儿的。(即init方法里没有IO操作)
这个版本实现简单,执行速度也快,不用来回上锁了。加锁版的就是给大汉分对象的时候先把大汉五花大绑地锁起来,这样的话就避免了在分配时他抢占多个对象的可能。分配完了之后再给大汉松下绑、解下锁,这样步骤一多,肯定是比较耗时的。
示例1:装饰时给每个类创建一个_instance属性

def Singleton(cls):
    print("正在进行装饰类", cls, '哦~')
    
    cls._instance = None

    def _singleton(*args, **kargs):
        if cls._instance: return cls._instance
        cls._instance = cls(*args, **kargs)
        return cls._instance

    return _singleton

@Singleton
class A(object):
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of ', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('__init__ of', self)
    

@Singleton
class B(object):
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of ', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('__init__ of', self)


if __name__ == '__main__':
    print()

    a1 = A(); a2 = A(); print(a1, a2)
    print()

    b1 = B(); b2 = B(); print(b1, b2)

示例2:装饰给创建一个空字典,生成对象时再把这个对象添加到字典里,下次如果查到字典里有所需对象的话就直接返回了,没有的话创建后再返回。

_instance = {}

def Singleton(cls):
    print("正在进行装饰类", cls, '哦~')

    global _instance

    def _singleton(*args, **kargs):
        if cls in _instance: return _instance[cls]
        _instance[cls] = cls(*args, **kargs)
        return _instance[cls]
    
    return _singleton


@Singleton
class A(object):
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of ', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('__init__ of', self)
    

@Singleton
class B(object):
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of ', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('__init__ of', self)


if __name__ == '__main__':
    print()

    a1 = A(); a2 = A(); print(a1, a2)
    print()

    b1 = B(); b2 = B(); print(b1, b2)

4.2.2. 加锁版

import threading

_lock = threading.Lock()

def Singleton(cls):
    print("正在进行装饰类", cls, '哦~')

    global _lock
    cls._instance = None

    def _singleton(*args, **kargs):
        if cls._instance: return cls._instance
        _lock.acquire()#上锁
        cls._instance = cls(*args, **kargs)
        _lock.release()#去锁
        return cls._instance
    
    return _singleton

@Singleton
class A(object):
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of ', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('__init__ of', self)
    

@Singleton
class B(object):
    @classmethod
    def __new__(cls, *args, **kwargs):
        print('__new__ of ', cls)
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print('__init__ of', self)


if __name__ == '__main__':
    print()

    a1 = A(); a2 = A(); print(a1, a2)
    print()

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

推荐阅读更多精彩内容