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. 簡書:http://www.lxweimin.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閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容