一天,小猿(程序猿)正在安靜的寫代碼(製造bug),突然狂風大作,只見,小猿的電腦屏幕出現一個無底的黑洞,小猿被捲入電腦,跌入洞中……
不知過了多久,小猿醒了過來,緩緩的睜開眼,發現自己躺在一個陌生的地方,而此時他眼前走過的是一個個機器人,這些機器人背後都有代號,有文件、內存、磁盤等等。小猿還沒來得及驚慌,就被一羣背後寫着“進程”的機器人組織開會,只見臺上坐着一個大佬,背後寫着赫赫的三個大字母:CPU,小猿心想,這是進了計算機內部窩了啊,望着身邊一個個面無表情的機器人,瞬間感覺弱小、可憐、無助、又飢餓……
原來,這位CPU大佬想做一個應用, 在整個應用的運行過程中,一直保持並維護着唯一的實例。所以組織大家集結思路,誰能做到,承諾一頓大餐作爲獎勵。小猿心想,這不就是單例嗎,就這?能難倒我?於是小猿本着助人爲樂(能喫飽飯)的原則,踊躍發言並給出了實現方案:
1.使用裝飾器方式實現
1.1函數裝飾器方式
def singleton(cls): # 創建一個字典用來保存被裝飾類的實例對象 _instance = {} def _singleton(*args, **kwargs): # 判斷這個類有沒有創建過對象,沒有新創建一個,有則返回之前創建的 if cls not in _instance: _instance[cls] = cls(*args, **kwargs) return _instance[cls] return _singleton @singleton class A(object): def __init__(self, a=0): self.a = a a1 = A(1) a2 = A(2) # id()函數可以獲取對象的內存地址,同一內存地址即爲同一對象 print(id(a1), id(a2))
1.2類裝飾器方式
class Singleton(object): def __init__(self, cls): self._cls = cls self._instance = {} def __call__(self): if self._cls not in self._instance: self._instance[self._cls] = self._cls() return self._instance[self._cls] @Singleton class B(object): def __init__(self): pass b1 = B() b2 = B() print(id(b1), id(b2))
CPU大佬看了搖了搖頭,若有所思,說,這樣是可以實現,但是裝飾器的原理和執行順序很複雜,我不能接受。
小猿反駁說:裝飾器是基於面向切面編程思想來實現的,具有很高的解耦性和靈活性,而且本質其實不難理解,裝飾器本質上其實還是一個python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。裝飾器的本質是函數,主要用來裝飾其他函數,也就是爲其他函數增加功能……
”打住打住“,沒等小猿說完,CPU就打斷了他,“我不想聽你這些繁瑣的解釋,還有其他實現方式嗎,沒有就退下吧!”
小猿心想:這真是一個難纏的人啊!爲了面子(主要還是想喫飯),忍了,給出方案2
2.使用類的方式實現
class Singleton(object): def __init__(self, *args, **kwargs): pass @classmethod def get_instance(cls, *args, **kwargs): # hasattr() 函數用於判斷對象是否包含對應的屬性,這裏是看看這個類有沒有_instance屬性 if not hasattr(Singleton, '_instance'): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance s1 = Singleton() # 使用這種方式創建實例的時候,並不能保證單例 s2 = Singleton.get_instance() # 只有使用這種方式創建的時候纔可以實現單例 s3 = Singleton() s4 = Singleton.get_instance() print(id(s1), id(s2), id(s3), id(s4))
小猿解釋道: 這種方式的思路就是,調用類的get_instance方法去創建對象,get_instance方法會判斷之前有沒有創建過對象,有的話也是會返回之前已經創建的對象,不再新創建,但是這樣有一個弊端,就是在使用類創建(s3 = Singleton()這種方式)的時候,就不能保證單例了,也就是說在創建類的時候一定要用類裏面規定的get_instance方法創建。
CPU說到:“那你解決一下嘍”。
小猿又開始在心裏嘀咕:站着說話不腰疼,我要不是看上那頓大餐,我纔不理你呢!給你方案3
3.使用__new__函數實現
class Singleton(object): def __init__(self): print("__init__") def __new__(cls, *args, **kwargs): print("__new__") if not hasattr(Singleton, "_instance"): print("創建新實例") Singleton._instance = object.__new__(cls) return Singleton._instance obj1 = Singleton() obj2 = Singleton() print(obj1, obj2)
當python實例化一個對象時,是先執行類的__new__方法,當我們沒寫__new__方法時,默認調用基類object的__new__方法,然後再執行類的__init__方法,對這個對象進行初始化,所有我們可以基於這個,去實現單例模式 ,我們通過hasattr(Singleton, "_instance")(其中hasattr()的功能是判斷一個對象有沒有指定的屬性)去判斷之前有沒有實例化過對象,如果有,就直接返回,沒有就新創建一個。
附上控制檯輸出:可以看出,同樣實現了單例。
這時CPU大佬說到:”看輸出其實執行了兩遍__init__方法,既然是同一個對象,初始化兩次,這不合理。“
”沒關係,那優化一下!“小猿說。
class Singleton(object): def __init__(self): if not hasattr(Singleton, "_first_init"): print("__init__") Singleton._first_init = True def __new__(cls, *args, **kwargs): print("__new__") if not hasattr(Singleton, "_instance"): print("創建新實例") Singleton._instance = object.__new__(cls) return Singleton._instance
通過控制檯輸出我們可以看到,__init__方法只執行了一次。
到這時,大佬似乎有些滿意。小猿又說道: ”爲了保證線程安全,我們還可以在類內部加入鎖機制!“
import threading class Singleton(object): _instance_lock = threading.Lock() # 線程鎖 def __init__(self): if not hasattr(Singleton, "_first_init"): print("__init__") Singleton._first_init = True def __new__(cls, *args, **kwargs): print("__new__") with Singleton._instance_lock: if not hasattr(Singleton, "_instance"): print("創建新實例") Singleton._instance = object.__new__(cls) return Singleton._instance def task(arg): obj = Singleton() print(obj) for i in range(10): t = threading.Thread(target=task, args=[i, ]) t.start()
此時,CPU大佬露出了欣慰的笑容,大讚,衆機器人也都歡呼着,CPU大佬也兌現了大餐的承諾,讓人帶着小猿去喫大餐。
小猿看到滿桌子的山珍海味,開心的合不攏嘴,大口大口地吃了起來……
叮鈴鈴,叮鈴鈴,鬧鈴響了……原來這一切只是小猿做的夢,而此時小猿的嘴裏還咬着一隻襪子……哈哈哈
藉此,梳理開發中經常用到的單例模式,其實python實現單例的方式還有很多,像通過模塊、metaclass方式等等,這裏不一一實現,感興趣的朋友可以自行百度。
最後,感謝女朋友在生活中,工作上的包容、理解與支持 !