前言
單例模式是設計模式(Design Pattern)中最簡單堤尾、最容易理解的一種,維基百科[1]的定義如下:
單例模式迁客,也叫單子模式郭宝,是一種常用的軟件設計模式。在應用這個模式時掷漱,單例對象的類必須保證只有一個實例存在粘室。許多時候整個系統只需要擁有一個的全局對象,這樣有利于我們協調系統整體的行為卜范。
單例模式的主要優(yōu)點是共享資源和減少資源消耗衔统,主要應用于IO或數據庫的線程池,緩存,日志,對話和需共享數據的資源等,但是在實現情況中濫用單例模式會帶來很多意想不到的問題海雪,本文重點在于介紹幾種Python實現單例模式的方法锦爵,這里就不再展開論述了。文中所演示的代碼都會托管在Github上奥裸。
簡單實現
首先险掀,我們先嘗試用Python內部類(嵌套類)來實現單例模式:
#coding=utf-8
class Singleton:
"""單列類
"""
class __MyClass:
"""實際生成實例的類
"""
def __init__(self, arg):
"""初始化并賦值"""
self.foo = arg
def display(self):
"""返回實例的id和屬性值"""
return (id(self), self.foo)
# 類屬性
_instance = None
def __init__(self, arg):
if not Singleton._instance:
Singleton._instance = Singleton.__MyClass(arg)
else:
Singleton._instance.foo = arg
def __getattr__(self, attr):
return getattr(self._instance, attr)
注意實際生成實例的類是內部的“__MyClass”類,前面的雙下劃線代表這是一個私有的類刺彩,用戶不能再外面直接訪問它迷郑。而在"__MyClass"類外封裝了一個“Singleton”類,這個類的任務就是在初始化時保證整個上下文中只有一個實例创倔,實現的方式很簡單嗡害。用一個私有屬性_instance保存當前生成的實例,在初始化時判斷實例是否為None畦攘,如果是就用“__MyClass”類生成一個新實例并賦值給_instance霸妹,否就直接返回或調用當前_instance的實例。最后用"__MyClass"里的實現的方法測試一下:
if __name__ == "__main__":
"""測試"""
s1 = Singleton("bar")
s2 = Singleton("zoo")
print(s1.display())
print(s2.display())
# output
>(41706760L, 'zoo')
>(41706760L, 'zoo')
基類
現在我們考慮將inner class拆分出來知押,因為在Python類實例化時會調用__new__方法[2]來生成實例叹螟,所以我們可以先繼承“Singleton”類,然后通過重寫基類的__new__方法讓其實現單例模式:
#coding=utf-8
class Singleton(object):
"""單例類
"""
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
class MyClass(Singleton):
"""實際生成實例的類
"""
def __init__(self, arg):
self.foo = arg
def display(self):
return (id(self), self.foo)
測試結果:
if __name__ == "__main__":
s1 = MyClass("bar")
s2 = MyClass("zoo")
print(s1.display())
print(s2.display())
assert s1 is s2
# output
>(40882416L, 'zoo')
>(40882416L, 'zoo')
裝飾器
第三種就是最常見的用裝飾器來實現單列模式:
#coding=utf-8
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class MyClass:
"""實際生成實例的類
"""
foo = "foo"
def display(self):
return (id(self))
@singleton
class OtherClass:
"""另一個類
"""
pass
裝飾器的實現過程是將生成的實例都放到一個名為instances的Dict中映射好台盯,這樣每次在類初始化時先檢查instances中是否已經包含有例化好的實例罢绽,有就直接返回是咧,沒有則調用類初始化一個并賦值給instances列表静盅。裝飾器的好處在于用一個Dict列表來管理所有需要實現單例模式的類良价,更簡便和通用化。代碼的測試結果如下:
if __name__ == "__main__":
s1 = MyClass()
s1.foo = "bar"
print(s1.display(), s1.foo)
s2 = MyClass()
s2.foo = "zoo"
print(s2.display(), s2.foo)
assert s1 is s2
s3 = OtherClass()
s4 = OtherClass()
assert s3 is s4
元類
如果希望不僅僅是通過限制而是在源頭上就創(chuàng)建一個單例類,我們需要用到元類來實現明垢,元類可以參考Stackoverflow[3]上的一個解答蚣常。簡單的說就是Python中的類也是一種對象,被稱為類對象痊银。類對象可以通過元類type來創(chuàng)建抵蚊,而在此過程中會調用type的 __call__ 方法。所以我們只要在type創(chuàng)建類對象的過程中重寫 __call__ 方法溯革,在其中加入相應的創(chuàng)建單例的邏輯即可實現單例模式贞绳,具體代碼實現如下:
#coding=utf-8
class Singleton(type):
def __call__(cls, *args, **kwargs):
"""重寫,實現單例模式"""
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instance
class MyClass(object):
# 指定元類
__metaclass__ = Singleton
def display(self):
return (id(self))
代碼的測試與前面類似鬓照,這里就不再累述了熔酷。
線程安全
最后,需要注意的是單例模式在多線程下可能會出現線程安全的問題豺裆,這時候就需要在單例的初始化過程中加上線程同步鎖來避免拒秘,但這樣又會降低整體的性能,具體可以參考這篇文檔臭猜。
參考
[1]維基百科
[2]Python官方文檔
[3]Stackoverflow