Python和Singleton (單件)模式[轉載]

來源:互聯網
上載者:User

一種在 python 中 Singleton mode 的實現如下:

class Foo: pass
def instance():
global inst
try:
inst
except:
inst = Foo ()
return inst

該實現的優點就是簡單和直觀,但缺點也同樣明顯:

  1. 需要客戶代碼顯式知道一個叫 instance() 的方法來建立該類的對象;
  2. 在並發環境下這種實現並不可靠;

第 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 (單件)模式

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.