設計模式之單例模式與工廠模式的Python實現(一) 談談Python中元類Metaclass(一):什麼是元類 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)實現(推薦)

關於元類的知識點,可以參考我之前寫的博文: 談談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中的單例模式的幾種實現方式的及優化

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章