一種在 python 中 Singleton mode 的實現如下:
class Foo: pass
def instance():
global inst
try:
inst
except:
inst = Foo ()
return inst
該實現的優點就是簡單和直觀,但缺點也同樣明顯:
- 需要客戶代碼顯式知道一個叫 instance() 的方法來建立該類的對象;
- 在並發環境下這種實現並不可靠;
第 2 點是相當嚴重的一個缺陷,如果你用了上面的代碼,那隻能祈禱不要有 1 個以上的執行個體出現(雖然幾率較低,但還是有可能),否則就會出現稀奇古怪的問題。
一個稍微好些實現如下:
class Singleton(object):
objs = {}
def __new__(cls, *args, **kv):
if cls in cls.objs:
return cls.objs[cls]
cls.objs[cls] = object.__new__(cls)
這個實現解決了第一個缺點,那些只需要一個執行個體的類想實現 Singleton mode ,只要從 Singleton 類繼承即可,無論在代碼的哪裡執行個體化該類,都只存在該類的一個執行個體。
為瞭解決第 2 個缺點,一個進化的版本出現了,如下:
class Singleton(object):
objs = {}
objs_locker = threading.Lock()
def __new__(cls, *args, **kv):
if cls in cls.objs:
return cls.objs[cls]
cls.objs_locker.acquire()
try:
if cls in cls.objs: ## double check locking
return cls.objs[cls]
cls.objs[cls] = object.__new__(cls)
finally:
cls.objs_locker.release()
是不是看著眼熟,對了,這就是在 Singleton mode 中經典的雙檢查鎖機制,該機制確保了在並發環境下 Singleton mode 的正確實現。
到此為止,上面提到的 2 個缺點都被進化後的代碼解決了,看上去已經很完美了,但是故事到此還沒有結束,不知道你是否看出來改進後的代碼還有什麼問題嗎?
再繼續之前,先介紹關於 __new__ 和 __init__ 的基礎知識, Python 的經典類和新式類都支援 __init__ 函數,但只有新式類支援 __new__ 函數,在一個新式類建立過程中, Python 解譯器會先調用該類的 __new__ 函數 建立 執行個體,然後在調用 __init__ 函數 初始化 這個執行個體,如果這些函數不存在,就會調用 Python預設提供的版本,但如果使用者提供了這些函數的實現,就會調用使用者實現的版本。
上面改進後的代碼也存在 2 個問題:
- 如果使用者提供了自訂版本的 __new__ 函數,會覆蓋或者幹擾到 Singleton 類中 __new__ 的執行,但是這種情況出現的機率極小,因為很少有使用者會 定製類執行個體 的建立過程;
- 如果使用者提供了自訂版本的 __init__ 函數,那麼每次執行個體化該類的時候, __init__ 都會被調用到,這顯然是不應該的, __init__ 只應該在建立執行個體的時候被調用一次;
為瞭解決 __init__ 被多次調用的問題,一個更進階(同時也更複雜)的版本如下:
class Singleton(object):
objs = {}
objs_locker = threading.Lock()
def __new__(cls, *args, **kv):
if cls in cls.objs:
return cls.objs[cls]['obj']
cls.objs_locker.acquire()
try:
if cls in cls.objs: ## double check locking
return cls.objs[cls]['obj']
obj = object.__new__(cls)
cls.objs[cls] = {'obj': obj, 'init': False}
setattr(cls, '__init__', cls.decorate_init(cls.__init__))
finally:
cls.objs_locker.release()
@classmethod
def decorate_init(cls, fn):
def init_wrap(*args):
if not cls.objs[cls]['init']:
fn(*args)
cls.objs[cls]['init'] = True
return
return init_wrap
看到這裡,你可能會想:一件簡單的事情,有必要搞的那麼複雜嗎?
我的回答是:根據情況而定。
- 如果你的運行環境不存在並發的情況,而且客戶代碼對額外的工作量(記住 instance() 這個函數)不在意的話,本文最開始的那段代碼是最適合的;
- 即使存在並發環境,但客戶代碼對額外的工作量(除了記住每個執行個體化函數,還要記得在同一個地方調用,並且要保證調用順序)還是不太在意的話,那就在程式啟動階段初始化所有的單件執行個體,本文最開始的那段代碼還是很合適的;
- 如果有並發存在,並且客戶很懶(懶是程式員的一種美德),不願意記住太多和業務無關的東西,也不願意費神集中初始化單件,並且還要保證初始化順序,而且集中初始化在某些情況下(如有外掛程式的系統中)是不可能的,那麼還是使用最後這段複雜的代碼吧。
有得必有失
簡單的實現,代碼邏輯清晰,維護量小,修改也很簡單,但是應用環境受限(上面前 2 點所述),並且一些初始化工作交由客戶來完成(調用 instance 函數),在小系統中這不是問題,但在一個大系統中,這會變成一個很明顯的負擔(特別是在執行個體化函數命名不統一的時候)。
複雜的實現,所有建立操作在一處完成,不對環境作假設,不給客戶帶來任何負擔,像普通類一樣使用,最重要的是將單件建立(基類)和業務代碼(繼承類)分開,劃分清晰;缺點是代碼複雜,不好維護和修改,需要一定的 Python 進階語言特性。
轉載自:Python和Singleton (單件)模式