1. 程式人生 > >設計模式之單例模式與工廠模式的Python實現(一)

設計模式之單例模式與工廠模式的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 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)實現(推薦)

以下是幾個關鍵的知識點:

  • 簡而言之,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