1. 單例模式
單例模式(Singleton Pattern)是一種常用的軟件設計模式,該模式的主要目的是確保某一個類只有一個實例存在。當你希望在整個系統中,某個類只能出現一個實例時,單例對象就能派上用場。
比如,某個服務器程序的配置信息存放在一個文件中,客戶端通過一個 AppConfig 的類來讀取配置文件的信息。如果在程序運行期間,有很多地方都需要使用配置文件的內容,也就是說,很多地方都需要創建 AppConfig 對象的實例,這就導致系統中存在多個 AppConfig 的實例對象,而這樣會嚴重浪費內存資源,尤其是在配置文件內容很多的情況下。事實上,類似 AppConfig 這樣的類,我們希望在程序運行期間只存在一個實例對象。
在 Python 中,我們可以用多種方法來實現單例模式
1.1 使用模塊(不推薦)
Python 的模塊就是天然的單例模式,因爲模塊在第一次導入時,會生成 .pyc
文件,當第二次導入時,就會直接加載 .pyc
文件,而不會再次執行模塊代碼。因此,我們只需把相關的函數和數據定義在一個模塊中,就可以獲得一個單例對象了。如果我們真的想要一個單例類,可以考慮這樣做:
mysingleton.py
class Singleton(object): def foo(self): pass singleton = Singleton()
將上面的代碼保存在文件 mysingleton.py
中,要使用時,直接在其他文件中導入此文件中的對象,這個對象即是單例模式的對象
from mysingleton import singleton
對這種使用模塊來實現單例模式的方法,只能在一些特定場景中使用(比如初始化一個config),因爲不好修改和維護。比如如果我想在代碼運行過程中,根據實時的參數生成一個單例,這種方法就不好用了。
1.2 使用裝飾器(不考慮多線程影響時可以使用)
>>> def Singleton(cls): _instance = {} def _singleton(*args,**kw): if cls not in _instance: _instance[cls] = cls(*args,**kw) return _instance[cls] return _singleton >>> @Singleton class A(object): a = 1 def __init__(self,x = 0): self.x = x >>> a1 = A(2) >>> a1 <__main__.A object at 0x000000893F3B0630> >>> a1.x 2 >>> a2 = A(3) >>> a2 <__main__.A object at 0x000000893F3B0630> >>> a2.x 2
有幾個知識點值得注意:
<1> 裝飾器不僅可以裝飾函數,也可以用來裝飾類
<2> 我們通過查看生成的對象a1和a2,發現他們的內存地址都是一樣的,是"0x000000893F3B0630",因此可以看出來是同一個對象,即驗證了我們的單例模式是成功的
<3> 執行a2 = A(3),我們可以看到a2.x = 2,而不是3。 這是因爲正常情況下語句a2 = A(3)會調用__init__函數,將3傳給x。但是由於我們是單例模式,a2 = A(3)並不會生成新的對象,而是將之前生成的a1的對象返回給了a2,因爲__init__函數只有在生成新的對象時纔會執行,所以a2.x = 2
但是這樣的實現方式是有漏洞的,當在多進程運行情況下,一旦__init__函數中有些耗時比較長的操作,會發生下面的情況:進程a和進程b同時執行,如果此時實例並沒有被生成,a和b會同時嘗試去生成實例,並且由於__init__耗時較長,a和b在生成實例時,都沒有現成的實例,就會造成a和b生成了不同的實例,我們的單例模式就失敗了。
下面是例子
>>> def Singleton(cls):
@functools.wrap(cls) _instance = {} def wrapper(*args,**kw): if cls not in _instance: _instance[cls] = cls(*args,**kw) return _instance[cls] return wrapper >>> import time >>> @Singleton class A(object): a = 1 def __init__(self,x = 0): time.sleep(2) self.x = x >>> import threading >>> def task(arg): obj = A(arg) print(obj, obj.x) >>> for i in range(10): t = threading.Thread(target = task, args=[i,]) t.start() >>> <__main__.A object at 0x000000893F5F8D30><__main__.A object at 0x000000893F5F82B0><__main__.A object at 0x000000893F5F8B38><__main__.A object at 0x000000893FBE4358><__main__.A object at 0x000000893FBE4160><__main__.A object at 0x000000893F5F8F28><__main__.A object at 0x000000893F5F8940><__main__.A object at 0x000000893FBE4550><__main__.A object at 0x000000893FBE4940><__main__.A object at 0x000000893FBE4748> 3026541798
通過這些實例的地址我們可以看出來,這也instance是不同的實例,我們的單例模式失敗了
1.3 使用類(基於__new__方法實現)
通過上面例子,我們可以知道,當我們實現單例時,爲了保證線程安全需要在內部加入鎖
我們知道,當我們實例化一個對象時,是先執行了類的__new__方法(我們沒寫時,默認調用object.__new__),實例化對象;然後再執行類的__init__方法,對這個對象進行初始化,所有我們可以基於這個,實現單例模式
import threading class Singleton(object): _instance_lock = threading.Lock() def __init__(self): pass def __new__(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"):
#第一個判斷,是爲了當單例對象生成後,不再鎖多進程 with Singleton._instance_lock: if not hasattr(Singleton, "_instance"):
#第二個判斷,是爲了當單例對象還未生成時,確保只有一個對象被生成。(如果沒有這一層判斷,當對象被第一個進程生成後,後續的進程還會生成新的對象) Singleton._instance = object.__new__(cls) return Singleton._instance obj1 = Singleton() obj2 = Singleton() print(obj1,obj2) def task(arg): obj = Singleton() print(obj) for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start()
值得注意的是類的實現中有兩個判斷:
第一個判斷,是爲了當單例對象生成後,不再鎖多進程
第二個判斷,是爲了當單例對象還未生成時,確保只有一個對象被生成。(如果沒有這一層判斷,當對象被第一個進程生成後,後續的進程還會生成新的對象)
下面是例子:
只有一個判斷,生成了多個實例。單例失敗。
>>> class Singleton(object): _instance_lock = threading.Lock() def __init__(self): time.sleep(1) @classmethod def instance(cls,*args,**kw): if not hasattr(Singleton,"_instance"): with Singleton._instance_lock: Singleton._instance = Singleton(*args,**kw) return Singleton._instance >>> def task(arg): obj = Singleton.instance() print(obj) >>> for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start() >>> <__main__.Singleton object at 0x000000893F5F8B38> <__main__.Singleton object at 0x000000893FBE44A8> <__main__.Singleton object at 0x000000893F5F8A90> <__main__.Singleton object at 0x000000893F5F8E10> <__main__.Singleton object at 0x000000893F5F87B8> <__main__.Singleton object at 0x000000893F5F8C18> <__main__.Singleton object at 0x000000893F5F8A90> <__main__.Singleton object at 0x000000893F5F8710> <__main__.Singleton object at 0x000000893F5F8BA8> <__main__.Singleton object at 0x000000893F5F8E10>
有兩個判斷,只生成了一個實例,單例成功
>>> class Singleton(object): _instance_lock = threading.Lock() def __init__(self): time.sleep(1) @classmethod def instance(cls,*args,**kw): if not hasattr(Singleton,"_instance"): with Singleton._instance_lock: if not hasattr(Singleton,"_instance"): Singleton._instance = Singleton(*args,**kw) return Singleton._instance >>> def task(arg): obj = Singleton.instance() print(obj) >>> for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start() >>> <__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90><__main__.Singleton object at 0x000000893F5F8A90>
我們需要特別注意的是,這種實現方法也不好,因爲是在單例模式的實現代碼,寫在了類的實現裏。我們每要實現一個單例模式的類,就要在類的定義裏寫相應的代碼。這樣沒有把設計模式和類的具體實現分離。
1.4 基於元類(metaclass)實現(推薦)
關於元類的知識點,可以參考我之前寫的博文: 談談Python中元類Metaclass(一):什麼是元類
以下是幾個關鍵的知識點:
- 簡而言之,metaclass是元類,用來動態創建class,在創建時根據metaclass的定義(比如__new__, __call__, __init__)控制。
- __new__()函數用來創建類的實例,__call__()函數是能讓實例可以被調用,__init__()函數用來對實例做一些初始化操作。從上而下看,在基於元類實現單例模式時,從屬關係爲 metaclass元類 --> 我們定義的class類(暫命名爲'A') --> 實現的單例對象'a';即我們定義的class 'A'是元類metaclass的對象,我們實現的單例對象'a'是定義的class 'A'的對象。而我們知道,單例對象創建時的語句是a=A(),也就是說,在這個語句執行時,我們實際上運行的是metaclass的__call__函數。因此我們要在__call__()函數中實現單例模式。
>>> import time >>> import threading >>> class SingletonType(type): _instance_lock = threading.Lock() def __call__(cls, *args, **kwargs): if not hasattr(cls, "_instance"): with SingletonType._instance_lock: if not hasattr(cls, "_instance"): cls._instance = super(SingletonType,cls).__call__(*args, **kwargs) return cls._instance >>> class Foo(metaclass=SingletonType): def __init__(self,name): self.name = name time.sleep(1) >>> def task(arg): obj = Foo(arg) print(obj,obj.name) >>> for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start() >>> <__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8><__main__.Foo object at 0x000000893F5F87B8> 0000000000
這樣,我們就完美的實現了單例模式,同時將達成了更優的目的:單例模式這個設計模式與類的定義分開。
參考鏈接:
1. 飄逸的python - __new__、__init__、__call__傻傻分不清: https://blog.csdn.net/handsomekang/article/details/46672251
2. Python中的單例模式的幾種實現方式的及優化