Python中的Singleton (單件)模式

 

我知道的一種在pythonSingleton mode的實現如下:

class Foo: pass

definstance():

    global inst

    try:

        inst

    except:

        inst = Foo()

    return inst

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

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

 

2點是相當嚴重的一個缺陷,如果你用了上面的代碼,那隻能祈禱不要有1個以上的實例出現(雖然機率較低,但還是有可能),否則就會出現稀奇古怪的問題。

一個稍微好些實現如下:

classSingleton(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個缺點,一個進化的版本出現了,如下:

classSingleton(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 checklocking

                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__被多次調用的問題,一個更高級(同時也更復雜)的版本如下:

classSingleton(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 checklocking

                return cls.objs[cls]['obj']

            obj = object.__new__(cls)

            cls.objs[cls] = {'obj': obj,'init': False}

            setattr(cls, '__init__',cls.decorate_init(cls.__init__))

            return cls.objs[cls]['obj']

        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高級語言特性。

 

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