設計模式之單例模式與工廠模式的Python實現(一)
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 notin _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)實現(推薦)
以下是幾個關鍵的知識點:
- 簡而言之,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
這樣,我們就完美的實現了單例模式,同時將達成了更優的目的:單例模式這個設計模式與類的定義分開。
參考連結: