一、單例模式
菜鳥教程-單例模式: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