[Python]單例模式

單例模式(Singleton Pattern)是一種常用的軟件設計模式焚挠,它用來確保一個類只有一個實例价脾,并提供一個全局訪問點梆暖。

當你想要在整個系統(tǒng)中確保某個類只有一個實例的時候莫换,單例模式就能派上用場了恩静。比如,客戶端通過一個AppConfig類來讀寫保存在配置文件中的信息沛善,在程序運行期間航揉,有多個地方需要讀取,修改配置信息金刁,如果每次讀寫都生成一個APPConfig類的實例帅涂,一來每次都從文件讀取影響性能议薪,二來程序在運行期間會有多個實例,浪費資源媳友。所以我們希望在程序運行期間只有一個AppConfig類的實例斯议。

Python實現(xiàn)單例模式有幾種方法:

  1. 使用模塊
  2. 使用__new__“構(gòu)造器”
  3. 使用裝飾器
  4. 使用元類

使用模塊

Python的模塊可以被多次import,但是只有在第一次import的時候才會load醇锚,load才會執(zhí)行模塊中的頂層代碼哼御,所以可以在一個模塊中生成類實例,并在其他模塊中import實例對象焊唬。例如:

class MySingleton(object):
    def foo(self):
        pass
 
singleton = MySingleton()

將上面的代碼保存在my_singleton.py文件中艇搀,在需要訪問MySingleton實例的模塊中import

from my_singleton import singleton
singleton.foo()

這種方法的優(yōu)點就是實現(xiàn)簡單,但是因為是基于Python的import求晶,load規(guī)則來實現(xiàn)的焰雕,所以也必然會被這個規(guī)則所影響,Python中有個reload()方法用來重新加載模塊芳杏,調(diào)用之后會從新生成新的實例矩屁,從而導致老的實例數(shù)據(jù)丟失。
所以使用模塊來實現(xiàn)單例模式爵赵,必須要注意不能reload模塊

使用__new__“構(gòu)造器”

__new__是一個特殊的“構(gòu)造器”吝秕,它在__init__之前執(zhí)行,它的職責就是為類創(chuàng)建一個實例對象空幻,之后它創(chuàng)建的實例會傳給__init__烁峭,所以可以通過它來實現(xiàn)單例模式。

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

class MySingleton(Singleton):
    pass

ms1 = MySingleton()
ms2 = MySingleton()
print id(ms1), id(ms2)

執(zhí)行之后輸出

222976992 222976992

兩個實例的id相同

使用裝飾器

可以用裝飾器來裝飾一個類秕铛,代碼如下:

from functools import wraps

def singleton(cls):
    _instance = {}
    
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)
        return _instance[cls]
    return get_instance
    
    
    
@singleton
class MySingleton(object):
    pass


print MySingleton, MySingleton.__name__

ms1 = MySingleton()
ms2 = MySingleton()

print id(ms1), id(ms2)

執(zhí)行后輸出

<function MySingleton at 0x000000000D3BC668> MySingleton
220845728 220845728

可以看到被裝飾器裝飾之后的類變成了一個方法约郁,不再是個類了〉剑可以通過修改裝飾器來解決這個問題鬓梅,修改后的裝飾器代碼如下:

def singleton2(cls):
    class class_w(cls):
        _instance = None
        def __new__(cls, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, cls).__new__(cls, *args, **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = cls.__name__
    return class_w

執(zhí)行之后輸出

<class '__main__.MySingleton'> MySingleton
223306584 223306584

使用元類

因為本人還未掌握元類相關(guān)的知識點,所以只能給出一個摘至網(wǎng)上的例子谨湘,代碼如下:
深入學習了下元類绽快,相關(guān)資料見我的《[Python] 元類(Metaclasses )探索》一文。
針對下面的代碼實現(xiàn)紧阔,我來解釋一下坊罢,元類Singleton的__call__方法會在由它創(chuàng)建的類MySingleton創(chuàng)建實例的時候調(diào)用;__call__方法被調(diào)用之后擅耽,首先檢查Singleton的類屬性_instances這個字典里是否已經(jīng)有了MySingleton這個類活孩,如果沒有則創(chuàng)建一個MySingleton類的實例保存到_instances中,最后返回_instances中保存的MySingleton的實例秫筏。
因為程序進程中只有一個Singleton元類對象诱鞠,它的類屬性_instances也是唯一的,所以通過Singleton元類創(chuàng)建的類这敬,不論初始化多少次獲取的實例都是同一個航夺。

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

#Python2
class MySingleton(object):
    __metaclass__ = Singleton

#Python3
#class MySingleton(metaclass=Singleton):
#    pass
    
s1 = MySingleton()  # 調(diào)用Singleton元類的\_\_call\_\_方法
s2 = MySingleton()
print id(s1), id(s2)

執(zhí)行之后輸出

223306920 223306920

其他網(wǎng)上搜到的方法

在網(wǎng)上還存在著這樣一種“實現(xiàn)”單例模式的方法

class Singleton(object):
    _state = {}
    def __new__(cls, *args, **kwargs):
        instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        instance.__dict__ = cls._state
        return instance
    
class MySingleton(Singleton):
    pass

s1 = MySingleton()
s2 = MySingleton()
s1.a = 2
s2.a = 3
print 'instance id:', id(s1), id(s2)
print 'instance dict id:', id(s1.__dict__), id(s2.__dict__)
print 'instance.a:', s1.a, s2.a

執(zhí)行之后輸出

instance id: 220846064 223307032
instance dict id: 222032488 222032488
instance.a: 3 3

可以看到,兩個實例對象的id不一樣崔涂,但是實例的__dict__相同阳掐,而我們知道__dict__是用來存儲實例對象的屬性的,兩個實例的__dict__相同冷蚂,也就是兩個實例的屬性相同缭保,間接達到了單例模式的其中一個效果。但是它不是單例模式蝙茶,來回顧下單例模式的定義艺骂,確保一個類只有一個實例,并提供一個全局訪問點隆夯,采用這種方式還是會生成多個實例钳恕,所以它不是單例模式。

線程安全的單例模式

上述的四種實現(xiàn)單例模式的方法除了第一種“使用模塊”之外蹄衷,全都不是線程安全的忧额。模塊因為是解釋器load的,而解釋器的GIL保證的線程安全愧口,所以它是線程安全的睦番。

剩下的三種方式,實現(xiàn)線程的安全的思路類似耍属,我就通過裝飾器方法來說明下托嚣。

import time
import threading
from multiprocessing.dummy import Pool as ThreadPool

def singleton_not_thread_safe(cls):
    _instances = {}
    def _instance(*args, **kwargs):
        if cls not in _instances:
            _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]
    return _instance


def singleton_thread_safe(cls):
    _instances = {}
    _lock = threading.Lock()
    def _instance(*args, **kwargs):
        if cls not in _instances:
            with _lock:
                if cls not in _instances:
                    _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]
    return _instance


@singleton_not_thread_safe
class NotThreadSafeSingleton(object):
    def __init__(self):
        time.sleep(1) # 模擬耗時I/O操作
        

@singleton_thread_safe
class ThreadSafeSingleton(object):
    def __init__(self):
        time.sleep(1)


def run(cls):
    result_set = set()
    result_set_access_lock = threading.Lock()
    
    def worker(cls, result_set, result_set_access_lock):
        instance= cls()
        with result_set_access_lock:
            result_set.add(instance)
    

    thread_pool = ThreadPool(10)
    for i in range(10):
        thread_pool.apply_async(worker, (cls,result_set, result_set_access_lock))
    thread_pool.close()
    thread_pool.join()

    
    print result_set
    print len(result_set)

    
print '-' * 20, 'Not Thread Safe', '-' * 20
run(NotThreadSafeSingleton)

print '-' * 20, 'Thread Safe', '-' * 20
run(ThreadSafeSingleton)

輸出

-------------------- Not Thread Safe --------------------
set([<__main__.NotThreadSafeSingleton object at 0x000000000D855C18>, <__main__.NotThreadSafeSingleton object at 0x000000000D855E48>, <__main__.NotThreadSafeSingleton object at 0x000000000D855860>, <__main__.NotThreadSafeSingleton object at 0x000000000D8ABCC0>, <__main__.NotThreadSafeSingleton object at 0x000000000D855CC0>, <__main__.NotThreadSafeSingleton object at 0x000000000D855320>, <__main__.NotThreadSafeSingleton object at 0x000000000D855D30>, <__main__.NotThreadSafeSingleton object at 0x000000000D855358>, <__main__.NotThreadSafeSingleton object at 0x000000000D855780>, <__main__.NotThreadSafeSingleton object at 0x000000000D8AB3C8>])
10
-------------------- Thread Safe --------------------
set([<__main__.ThreadSafeSingleton object at 0x000000000D8AB6A0>])
1

可以看到非線程安全的代碼,會生成10個實例厚骗。
線程安全的核心代碼:

def singleton_thread_safe(cls):
    _instances = {}
    _lock = threading.Lock()
    def _instance(*args, **kwargs):
        if cls not in _instances:
            with _lock:
                if cls not in _instances:
                    _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]
    return _instance

可以看到在閉包里采用了“雙重檢查加鎖”方法來實現(xiàn)線程安全

總結(jié)

好了注益,關(guān)于Python語言的單例模式我們就講到這里,現(xiàn)在來回顧下溯捆,可以通過模塊丑搔,__new__“構(gòu)造器”,裝飾器提揍,元類四種方法來實現(xiàn)單例模式啤月,另外除了模塊之外,其他三種方式都不是線程安全的劳跃,但可以通過“雙重檢查加鎖”的方法來實現(xiàn)線程安全谎仲。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刨仑,隨后出現(xiàn)的幾起案子郑诺,更是在濱河造成了極大的恐慌夹姥,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辙诞,死亡現(xiàn)場離奇詭異辙售,居然都是意外死亡,警方通過查閱死者的電腦和手機飞涂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門旦部,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人较店,你說我怎么就攤上這事士八。” “怎么了梁呈?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵婚度,是天一觀的道長。 經(jīng)常有香客問我官卡,道長陕见,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任味抖,我火速辦了婚禮评甜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仔涩。我一直安慰自己忍坷,他們只是感情好,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布熔脂。 她就那樣靜靜地躺著佩研,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霞揉。 梳的紋絲不亂的頭發(fā)上旬薯,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音适秩,去河邊找鬼绊序。 笑死,一個胖子當著我的面吹牛秽荞,可吹牛的內(nèi)容都是我干的骤公。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼扬跋,長吁一口氣:“原來是場噩夢啊……” “哼阶捆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤洒试,失蹤者是張志新(化名)和其女友劉穎倍奢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垒棋,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡卒煞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捕犬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跷坝。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡酵镜,死狀恐怖碉碉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淮韭,我是刑警寧澤垢粮,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站靠粪,受9級特大地震影響蜡吧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜占键,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一昔善、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畔乙,春花似錦君仆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至牍鞠,卻和暖如春咖摹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背难述。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工萤晴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胁后。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓硫眯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親择同。 傳聞我的和親對象是個殘疾皇子两入,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 我講這個故事的時候,腦中浮現(xiàn)的是我的好友小堡敲才,我大概永遠記得那一次她走過初中班主任的身邊裹纳,冷冷的一句話:這個世界要...
    因果音閱讀 258評論 0 0
  • 若不是緣分秒青煙繞白雨飄初見你 你身著一襲白衣择葡,棗紅色的眼鏡漏出人間的奧秘。 若不是無人處剃氧,秋月出敏储,羌笛嗚,你手織...
    霜葉紅于2月花閱讀 259評論 1 1
  • 最近迷上了相學朋鞍,緣起是看了曾國藩的書籍已添,據(jù)說曾公在許多家書中給人看像,之后每每應驗滥酥,于是興起更舞,搜索相術(shù)。 ...
    豬毛雞蛋皮閱讀 2,304評論 0 2