[Python]單例模式

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

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

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

  1. 使用模塊
  2. 使用__new__“構造器”
  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__“構造器”

__new__是一個特殊的“構造器”,它在__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

使用元類

因為本人還未掌握元類相關的知識點,所以只能給出一個摘至網(wǎng)上的例子,代碼如下:
深入學習了下元類,相關資料見我的《[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)線程安全

總結

好了,關于Python語言的單例模式我們就講到這里,現(xiàn)在來回顧下,可以通過模塊,__new__“構造器”,裝飾器,元類四種方法來實現(xiàn)單例模式,另外除了模塊之外,其他三種方式都不是線程安全的,但可以通過“雙重檢查加鎖”的方法來實現(xiàn)線程安全。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容