用Python實現設計模式——單例模式

前言

單例模式是設計模式(Design Pattern)中最簡單堤尾、最容易理解的一種,維基百科[1]的定義如下:

單例模式迁客,也叫單子模式郭宝,是一種常用的軟件設計模式。在應用這個模式時掷漱,單例對象的必須保證只有一個實例存在粘室。許多時候整個系統只需要擁有一個的全局對象,這樣有利于我們協調系統整體的行為卜范。

單例模式的主要優(yōu)點是共享資源和減少資源消耗衔统,主要應用于IO或數據庫的線程池,緩存,日志,對話和需共享數據的資源等,但是在實現情況中濫用單例模式會帶來很多意想不到的問題海雪,本文重點在于介紹幾種Python實現單例模式的方法锦爵,這里就不再展開論述了。文中所演示的代碼都會托管在Github上奥裸。

簡單實現

首先险掀,我們先嘗試用Python內部類(嵌套類)來實現單例模式:

#coding=utf-8
class Singleton:
    """單列類
    """
    class __MyClass:
        """實際生成實例的類
        """
        def __init__(self, arg):
            """初始化并賦值"""
            self.foo = arg

        def display(self):
            """返回實例的id和屬性值"""
            return (id(self), self.foo)

    # 類屬性
    _instance = None
    def __init__(self, arg):
        if not Singleton._instance:
            Singleton._instance = Singleton.__MyClass(arg)
        else:
            Singleton._instance.foo = arg

    def __getattr__(self, attr):
        return getattr(self._instance, attr)

注意實際生成實例的類是內部的“__MyClass”類,前面的雙下劃線代表這是一個私有的類刺彩,用戶不能再外面直接訪問它迷郑。而在"__MyClass"類外封裝了一個“Singleton”類,這個類的任務就是在初始化時保證整個上下文中只有一個實例创倔,實現的方式很簡單嗡害。用一個私有屬性_instance保存當前生成的實例,在初始化時判斷實例是否為None畦攘,如果是就用“__MyClass”類生成一個新實例并賦值給_instance霸妹,否就直接返回或調用當前_instance的實例。最后用"__MyClass"里的實現的方法測試一下:

if __name__ == "__main__":
    """測試"""
    s1 = Singleton("bar")
    s2 = Singleton("zoo")
    print(s1.display())
    print(s2.display())

# output
>(41706760L, 'zoo')
>(41706760L, 'zoo')

基類

現在我們考慮將inner class拆分出來知押,因為在Python類實例化時會調用__new__方法[2]來生成實例叹螟,所以我們可以先繼承“Singleton”類,然后通過重寫基類的__new__方法讓其實現單例模式:

#coding=utf-8
class Singleton(object):
    """單例類
    """
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

class MyClass(Singleton):
    """實際生成實例的類
    """
    def __init__(self, arg):
        self.foo = arg

    def display(self):
        return (id(self), self.foo)

測試結果:

if __name__ == "__main__":
    s1 = MyClass("bar")
    s2 = MyClass("zoo")
    print(s1.display())
    print(s2.display())
    assert s1 is s2

# output
>(40882416L, 'zoo')
>(40882416L, 'zoo')

裝飾器

第三種就是最常見的用裝飾器來實現單列模式:

#coding=utf-8
def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class MyClass:
    """實際生成實例的類
    """
    foo = "foo"
    def display(self):
        return (id(self))

@singleton
class OtherClass:
    """另一個類
    """
    pass

裝飾器的實現過程是將生成的實例都放到一個名為instancesDict中映射好台盯,這樣每次在類初始化時先檢查instances中是否已經包含有例化好的實例罢绽,有就直接返回是咧,沒有則調用類初始化一個并賦值給instances列表静盅。裝飾器的好處在于用一個Dict列表來管理所有需要實現單例模式的類良价,更簡便和通用化。代碼的測試結果如下:

if __name__ == "__main__":
    s1 = MyClass()
    s1.foo = "bar"
    print(s1.display(), s1.foo)
    s2 = MyClass()
    s2.foo = "zoo"
    print(s2.display(), s2.foo)
    assert s1 is s2
    s3 = OtherClass()
    s4 = OtherClass()
    assert s3 is s4

元類

如果希望不僅僅是通過限制而是在源頭上就創(chuàng)建一個單例類,我們需要用到元類來實現明垢,元類可以參考Stackoverflow[3]上的一個解答蚣常。簡單的說就是Python中的類也是一種對象,被稱為類對象痊银。類對象可以通過元類type來創(chuàng)建抵蚊,而在此過程中會調用type__call__ 方法。所以我們只要在type創(chuàng)建類對象的過程中重寫 __call__ 方法溯革,在其中加入相應的創(chuàng)建單例的邏輯即可實現單例模式贞绳,具體代碼實現如下:

#coding=utf-8
class Singleton(type):
    def __call__(cls, *args, **kwargs):
        """重寫,實現單例模式"""
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instance

class MyClass(object):
    # 指定元類
    __metaclass__ = Singleton

    def display(self):
        return (id(self))

代碼的測試與前面類似鬓照,這里就不再累述了熔酷。

線程安全

最后,需要注意的是單例模式在多線程下可能會出現線程安全的問題豺裆,這時候就需要在單例的初始化過程中加上線程同步鎖來避免拒秘,但這樣又會降低整體的性能,具體可以參考這篇文檔臭猜。

參考

[1]維基百科
[2]Python官方文檔
[3]Stackoverflow

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末躺酒,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子蔑歌,更是在濱河造成了極大的恐慌羹应,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件次屠,死亡現場離奇詭異园匹,居然都是意外死亡,警方通過查閱死者的電腦和手機劫灶,發(fā)現死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門裸违,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人本昏,你說我怎么就攤上這事供汛。” “怎么了涌穆?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵怔昨,是天一觀的道長。 經常有香客問我宿稀,道長趁舀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任祝沸,我火速辦了婚禮赫编,結果婚禮上巡蘸,老公的妹妹穿的比我還像新娘。我一直安慰自己擂送,他們只是感情好,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布唯欣。 她就那樣靜靜地躺著嘹吨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪境氢。 梳的紋絲不亂的頭發(fā)上蟀拷,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音萍聊,去河邊找鬼问芬。 笑死,一個胖子當著我的面吹牛寿桨,可吹牛的內容都是我干的此衅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼亭螟,長吁一口氣:“原來是場噩夢啊……” “哼挡鞍!你這毒婦竟也來了?” 一聲冷哼從身側響起预烙,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤墨微,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扁掸,有當地人在樹林里發(fā)現了一具尸體翘县,經...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年谴分,在試婚紗的時候發(fā)現自己被綠了锈麸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡狸剃,死狀恐怖掐隐,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情钞馁,我是刑警寧澤虑省,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站僧凰,受9級特大地震影響探颈,放射性物質發(fā)生泄漏。R本人自食惡果不足惜训措,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一伪节、第九天 我趴在偏房一處隱蔽的房頂上張望光羞。 院中可真熱鬧,春花似錦怀大、人聲如沸纱兑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潜慎。三九已至,卻和暖如春蓖康,著一層夾襖步出監(jiān)牢的瞬間铐炫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工蒜焊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留倒信,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓泳梆,卻偏偏與公主長得像鳖悠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸭丛,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內容

  • http://python.jobbole.com/85231/ 關于專業(yè)技能寫完項目接著寫寫一名3年工作經驗的J...
    燕京博士閱讀 7,574評論 1 118
  • 單例模式(SingletonPattern)一般被認為是最簡單竞穷、最易理解的設計模式,也因為它的簡潔易懂鳞溉,是項目中最...
    成熱了閱讀 4,253評論 4 34
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理瘾带,服務發(fā)現,斷路器熟菲,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 近日抄罕,擁有30多年歷史的老牌傳統教輔機構《英語周報》線上課程正式登陸滬江旗下實時互動教育品牌CCtalk允蚣。首批上線...
    滬江中小幼閱讀 298評論 0 1
  • ①我要的是,老公時不時拿錢給我呆贿,讓我去買衣服鞋子嚷兔,讓我隨便花。這種感覺太棒了做入。 ②我要的是冒晰,老公每晚睡覺都抱抱我,...
    心靈驛站園閱讀 303評論 0 0