python實現單例模式詳解

一、單例模式

菜鳥教程-單例模式:https://www.runoob.com/design-pattern/singleton-pattern.html

二、python實現單例模式錯誤的示範

在網上看到的一個例子是使用雙檢鎖實現單例模式,這個方法通過重載python對象的__new__ 方法,使得每個類只能被new一次。代碼如下:

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)

上面的代碼看似實現了單例模式,但是隻是實現了一個單例模式的外殼,爲什麼這麼說呢,我們在__init__函數里加一個打印語句看看。

import threading


class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        ptint('__init__ is called.')

    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)

運行一下我們就會發現 __init__ 函數調用了兩次,這是這段代碼最大的問題,每次調用類時 __init__ 函數都會調用一次。雖然類只會被new一次,但是類的屬性卻會在類的使用過程中被不斷覆蓋,所以上面的代碼只做到了類的單例,但是不能做到屬性的單例。

有人說既然這樣我把屬性全部放在 __new__ 函數裏初始化不就行了,這個做法在功能上沒有問題,但是卻違反了單一職責原則,__new__ 函數並不是負責初始化屬性的功能,__init__ 函數纔是。

另外上面的代碼中將 Singleton 硬編碼到了代碼中,使得這個類不能被繼承,因爲當子類調用父類的 __new__ 函數時返回的不是子類的類型。所以我們需要將 Singleton 改成 cls__new__ 函數接受的類的type對象。

三、正確的示範

上面我們提到了 __init__ 函數調用多次的問題,也說明了直接在 __new__ 函數裏初始化屬性的問題,現在我們就來討論一下如何正確的用 python實現單例模式。

我們現在面臨的問題就是如何讓 __init__ 函數只調用一次,最簡單的思路就是讓 __init__ 函數和 __new__ 函數一樣,也使用一個標誌和雙檢鎖來確保線程安全和只調用一次,修改後的代碼如下:

import threading


class Singleton(object):
    _lock = threading.Lock()

    def __init__(self):
        if not hasattr(self, '_init_flag'):
            with self._lock:
                if not hasattr(self, '_init_flag'):
                    self._init_falg = True
                    # 初始化屬性
        			ptint('__init__ is called.')

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            with cls._instance_lock:
                if not hasattr(cls, "_instance"):
                    cls._instance = object.__new__(cls)  
        return cls._instance


obj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)

現在我們的單例模式總算有點樣子了,Singleton 類的 __new____init__ 函數都只會調用一次,並且這些都是線程安全的。

但是這樣還不夠,按照現在的方法,我們每次要定義一個單例模式的類時都需要手動去修改 __init__ 函數和 __new__ 函數,這有點麻煩。如果我們用的是 Java的話那就沒辦法了,這些麻煩事必要的,但我們使用的語言是python!

四、使用裝飾器實現單例模式

從上一步單例模式的實現來看,我們每次要做到就是修改 __init__ 函數和 __new__ 函數,這簡直就是爲裝飾器量身定做的應用場景。我們可以使用裝飾器來替換類的 __init__ 函數和 __new__ 函數,將類原來的函數放在雙檢鎖內部執行。代碼如下:

from functools import wraps
import threading


def singleton():
    """
    單例模式裝飾器
    :return:
    """
  	# 閉包綁定線程鎖
    lock = threading.Lock()
    def decorator(cls):
        # 替換 __new__ 函數
        instance_attr = '_instance'
        # 獲取原來的__new__函數 防止無限遞歸
		__origin_new__ = cls.__new__
        @wraps(__origin_new__)
        def __new__(cls_1, *args, **kwargs):
            if not hasattr(cls_1, instance_attr):
                with lock:
                    if not hasattr(cls_1, instance_attr):
                        setattr(cls_1, instance_attr, __origin_new__(cls_1, *args, **kwargs))
            return getattr(cls_1, instance_attr)
        cls.__new__ = __new__
		
        # 替換 __init__函數 原理同上
        init_flag = '_init_flag'
        __origin_init__ = cls.__init__
        @wraps(__origin_init__)
        def __init__(self, *args, **kwargs):
            if not hasattr(self, init_flag):
                with lock:
                    if not hasattr(self, init_flag):
                        __origin_init__(self, *args, **kwargs)
                        setattr(self, init_flag, True)
        cls.__init__ = __init__
        return cls
    return decorator

使用方法非常簡單:

@singleton()
class Test:
	def __init__(self):
        # do something
        pass

需要注意的是裝飾器要加括號,這是爲了給每個類綁定一個線程鎖,具體原理與單例模式無關,這裏就不贅述了。另外使用了裝飾器的類不需要修改 __new__ 函數,和普通的類一樣使用就行。關於這個裝飾器的具體實現原理我會找時間再寫一篇博客。

參考

菜鳥教程-單例模式:https://www.runoob.com/design-pattern/singleton-pattern.html

博客園-聽風。-Python中的單例模式的幾種實現方式的及優化:https://www.cnblogs.com/huchong/p/8244279.html

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