問題:
現代化的巧克力工廠具有計算機控制的巧克力鍋爐。鍋爐作的事情就是把巧克力和牛奶融在一塊兒,而後送到下一個階段,以製成巧克力棒。下邊是一個巧克力公司鍋爐控制器的代碼,仔細觀察一下,這段代碼有什麼問題?html
class ChocolateBoiler(object): def __init__(self): self.empty = True self.boiled = False def fill(self): # 向鍋爐填充巧克力和牛奶混合物 # 在鍋爐內填充原料時,鍋爐必須是空的。 # 一旦填入原料,就要把empty 和 boiled 標誌設置好 if self.empty: self.empty = False self.boiled = False def drain(self): # 排出煮沸的巧克力和牛奶 # 鍋爐排出時,必須是滿的且煮沸的。 # 排出完畢empty 設置爲 true if not self.empty and self.boiled: self.empty = True def boil(self): # 將顱內物煮沸 # 煮混合物時,鍋爐內必須是滿的且沒有煮沸過 # 一旦煮沸,就把 boiled 設置爲 true if not self.empty and not self.boiled: self.boiled = True
從代碼能夠看出,他們加入了多種判斷,以防止很差的事情發生。若是同時存在兩個ChocolateBoiler
實例,那這麼多判斷豈不是失去做用了。那咱們改如何實現這個需求呢?這個問題的核心是,咱們要先判斷實例是否是已經存在,若是存在就再也不建立。python
_chocolate_boiler_instance = None # 聲明實例 def chocolate_boiler(): global _chocolate_boiler_instance # 使用全局變量 if _chocolate_boiler_instance is not None: # 判斷是否存在,若是存在,直接返回 return _chocolate_boiler_instance else: # 若是不存在,建立一個新的 _chocolate_boiler_instance = ChocolateBoiler() return _chocolate_boiler_instance
如今咱們須要獲取 ChocolateBoiler
實例的時候只須要調用 chocolate_boiler 方法獲取實例便可保證同時只有一個 ChocolateBoiler
實例。git
這種保證 ChocolateBoiler
類只有一個實例,並提供一個全局訪問點的模式,就是單例模式
。github
單例模式:
確保一個類只有一個實例,並提供一個全局訪問點。api
python 實現單例模式有多種方案:函數
《python cookbook》提供了很是易用的 Singleton
類,只要繼承它,就會成爲單例。ui
# python 3 代碼實現 class Singleton(type): def __init__(self, *args, **kwargs): self.__instance = None super().__init__(*args, **kwargs) def __call__(self, *args, **kwargs): if self.__instance is None: # 若是 __instance 不存在,建立新的實例 self.__instance = super().__call__(*args, **kwargs) return self.__instance else: # 若是存在,直接返回 return self.__instance class Spam(metaclass=Singleton): def __init__(self): print('Creating Spam') a = Spam() b = Spam() print(a is b) # 這裏輸出爲 True
元類(metaclass)能夠控制類的建立過程,它主要作三件事:spa
例子中咱們構造了一個Singleton元類,並使用__call__方法使其可以模擬函數的行爲。構造類 Spam 時,將其元類設爲Singleton,那麼建立類對象 Spam 時,行爲發生以下:設計
Spam = Singleton(name,bases,class_dict),Spam 其實爲Singleton類的一個實例。code
建立 Spam 的實例時,Spam()=Singleton(name,bases,class_dict)()=Singleton(name,bases,class_dict).__call__(),這樣就將 Spam 的全部實例都指向了 Spam 的屬性 __instance上。
咱們能夠使用 new 來控制實例的建立過程,代碼以下:
class Singleton(object): __instance = None def __new__(cls, *args, **kw): if not cls.__instance: cls.__instance = super().__new__(cls, *args, **kw) return cls.__instance class Foo(Singleton): a = 1 one = Foo() two = Foo() assert one == two assert one is two assert id(one) == id(two)
經過 new 方法,將類的實例在建立的時候綁定到類屬性 __instance 上。若是cls.__instance 爲None,說明類還未實例化,實例化並將實例綁定到cls.__instance 之後每次實例化的時候都返回第一次實例化建立的實例。注意從Singleton派生子類的時候,不要重載__new__。
import functools def singleton(cls): ''' Use class as singleton. ''' # 首先將 __new__ 方法賦值給 __new_original__ cls.__new_original__ = cls.__new__ @functools.wraps(cls.__new__) def singleton_new(cls, *args, **kw): # 嘗試從 __dict__ 取 __it__ it = cls.__dict__.get('__it__') if it is not None: # 若是有值,說明實例已經建立,返回實例 return it # 若是實例不存在,使用 __new_original__ 建立實例,並將實例賦值給 __it__ cls.__it__ = it = cls.__new_original__(cls, *args, **kw) it.__init_original__(*args, **kw) return it # class 將原有__new__ 方法用 singleton_new 替換 cls.__new__ = singleton_new cls.__init_original__ = cls.__init__ cls.__init__ = object.__init__ return cls # # 使用示例 # @singleton class Foo: def __new__(cls): cls.x = 10 return object.__new__(cls) def __init__(self): assert self.x == 10 self.x = 15 assert Foo().x == 15 Foo().x = 20 assert Foo().x == 20
這種方法的內部實現和使用 __new__
相似:
將名字singleton綁定到實例上,singleton就是它本身類的惟一對象了。
class singleton(object): pass singleton = singleton()
https://github.com/gusibi/Metis/blob/master/apis/v1/schemas.py#L107 使用的就是這種方式,用來獲取全局的 request
Python 的模塊就是自然的單例模式,由於模塊在第一次導入時,會生成 .pyc 文件,當第二次導入時,就會直接加載 .pyc 文件,而不會再次執行模塊代碼。所以,咱們只需把相關的函數和數據定義在一個模塊中,就能夠得到一個單例對象了。
最後,感謝女友支持。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|
![]() |
![]() |