單例模式(Singleton Pattern)是一種常用的軟件設計模式焚挠,它用來確保一個類只有一個實例价脾,并提供一個全局訪問點梆暖。
當你想要在整個系統(tǒng)中確保某個類只有一個實例的時候莫换,單例模式就能派上用場了恩静。比如,客戶端通過一個AppConfig類來讀寫保存在配置文件中的信息沛善,在程序運行期間航揉,有多個地方需要讀取,修改配置信息金刁,如果每次讀寫都生成一個APPConfig類的實例帅涂,一來每次都從文件讀取影響性能议薪,二來程序在運行期間會有多個實例,浪費資源媳友。所以我們希望在程序運行期間只有一個AppConfig類的實例斯议。
Python實現(xiàn)單例模式有幾種方法:
- 使用模塊
- 使用__new__“構(gòu)造器”
- 使用裝飾器
- 使用元類
使用模塊
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)線程安全谎仲。