問題:
現代化的巧克力工廠具備計算機控制的巧克力鍋爐。鍋爐做的事情就是把巧克力和牛奶融在一起,然後送到下一個階段,以製成巧克力棒。下邊是一個巧克力公司鍋爐控制器的程式碼,仔細觀察一下,這段程式碼有什麼問題?
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
例項,那這麼多判斷豈不是失去作用了。那我們改如何實現這個需求呢?這個問題的核心是,我們要先判斷例項是不是已經存在,如果存在就不再建立。
_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
例項。
這種保證 ChocolateBoiler
類只有一個例項,並提供一個全域性訪問點的模式,就是單例模式
。
單例模式
定義
單例模式:
確保一個類只有一個例項,並提供一個全域性訪問點。
- 也就是說,我們使用單例模式要把某個類設計成自己管理的一個單獨例項,同時也避免其他類再自行產生例項。並且只允許通過單例類獲取單例的例項。
- 我們也提供對這個例項的全域性訪問點:當你需要例項時,像類查詢,它會返回單個例項。
實現
python 實現單例模式有多種方案:
使用 metaclass
《python cookbook》提供了非常易用的 Singleton
類,只要繼承它,就會成為單例。
# 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)可以控制類的建立過程,它主要做三件事:
- 攔截類的建立
- 修改類的定義
- 返回修改後的類
例子中我們構造了一個Singleton元類,並使用call方法使其能夠模擬函式的行為。構造類 Spam 時,將其元類設為Singleton,那麼建立類物件 Spam 時,行為發生如下:
Spam = Singleton(name,bases,class_dict),Spam 其實為Singleton類的一個例項。
建立 Spam 的例項時,Spam()=Singleton(name,bases,class_dict)()=Singleton(name,bases,class_dict).call(),這樣就將 Spam 的所有例項都指向了 Spam 的屬性 __instance上。
使用 new
我們可以使用 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__
類似:
- 首先,將 new 方法賦值給 new_original,原有 new 方法用 singleton_new 替換,定義 init_original 並將 cls.init 賦值給 init_original
- 在 singleton_new 方法內部,嘗試從 dict 取 it(例項)
- 如果例項不存在,使用 new_original 建立例項,並將例項賦值給 it,然後返回例項
最簡單的方式
將名字singleton繫結到例項上,singleton就是它自己類的唯一物件了。
class singleton(object):
pass
singleton = singleton()複製程式碼
github.com/gusibi/Meti… 使用的就是這種方式,用來獲取全域性的 request
Python 的模組就是天然的單例模式,因為模組在第一次匯入時,會生成 .pyc 檔案,當第二次匯入時,就會直接載入 .pyc 檔案,而不會再次執行模組程式碼。因此,我們只需把相關的函式和資料定義在一個模組中,就可以獲得一個單例物件了。
參考連結
最後,感謝女朋友支援。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|