Python單例模式(Singleton)的N種實(shí)現(xiàn)
入門教程、案例源碼、學(xué)習(xí)資料、讀者群
請?jiān)L問: python666.cn
很多初學(xué)者喜歡用全局變量,因?yàn)檫@比函數(shù)的參數(shù)傳來傳去更容易讓人理解。確實(shí)在很多場景下用全局變量很方便。不過如果代碼規(guī)模增大,并且有多個文件的時候,全局變量就會變得比較混亂。你可能不知道在哪個文件中定義了相同類型甚至重名的全局變量,也不知道這個變量在程序的某個地方被做了怎樣的操作。
因此對于這種情況,有種更好的實(shí)現(xiàn)方式:
單例(Singleton)
單例是一種設(shè)計模式,應(yīng)用該模式的類只會生成一個實(shí)例。
單例模式保證了在程序的不同位置都可以且僅可以取到同一個對象實(shí)例:如果實(shí)例不存在,會創(chuàng)建一個實(shí)例;如果已存在就會返回這個實(shí)例。因?yàn)閱卫且粋€類,所以你也可以為其提供相應(yīng)的操作方法,以便于對這個實(shí)例進(jìn)行管理。
舉個例子來說,比如你開發(fā)一款游戲軟件,游戲中需要有“場景管理器”這樣一種東西,用來管理游戲場景的切換、資源載入、網(wǎng)絡(luò)連接等等任務(wù)。這個管理器需要有多種方法和屬性,在代碼中很多地方會被調(diào)用,且被調(diào)用的必須是同一個管理器,否則既容易產(chǎn)生沖突,也會浪費(fèi)資源。這種情況下,單例模式就是一個很好的實(shí)現(xiàn)方法。
單例模式廣泛應(yīng)用于各種開發(fā)場景,對于開發(fā)者而言是必須掌握的知識點(diǎn),同時在很多面試中,也是常見問題。本篇文章總結(jié)了目前主流的實(shí)現(xiàn)單例模式的方法供讀者參考。
希望看過此文的同學(xué),在以后被面到此問題時,能直接皮一下面試官,“我會 4 種單例模式實(shí)現(xiàn),你想聽哪一種?”
以下是實(shí)現(xiàn)方法索引:
使用函數(shù)裝飾器實(shí)現(xiàn)單例
使用類裝飾器實(shí)現(xiàn)單例
使用 __new__ 關(guān)鍵字實(shí)現(xiàn)單例
使用 metaclass 實(shí)現(xiàn)單例
使用函數(shù)裝飾器實(shí)現(xiàn)單例
以下是實(shí)現(xiàn)代碼:
def singleton(cls):
_instance = {}
def inner():
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls]
return inner
@singleton
class Cls(object):
def __init__(self):
pass
cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))
輸出結(jié)果:
True
在 Python 中,id 關(guān)鍵字可用來查看對象在內(nèi)存中的存放位置,這里 cls1 和 cls2 的 id 值相同,說明他們指向了同一個對象。
關(guān)于裝飾器的知識,有不明白的同學(xué)可以查看之前的文章 【編程課堂】裝飾器淺析 或者使用搜索引擎再學(xué)習(xí)一遍。代碼中比較巧妙的一點(diǎn)是:
_instance = {}
使用不可變的類地址作為鍵,其實(shí)例作為值,每次創(chuàng)造實(shí)例時,首先查看該類是否存在實(shí)例,存在的話直接返回該實(shí)例即可,否則新建一個實(shí)例并存放在字典中。
使用類裝飾器實(shí)現(xiàn)單例
代碼:
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 Cls2(object):
def __init__(self):
pass
cls1 = Cls2()
cls2 = Cls2()
print(id(cls1) == id(cls2))
同時,由于是面對對象的,這里還可以這么用
class Cls3():
pass
Cls3 = Singleton(Cls3)
cls3 = Cls3()
cls4 = Cls3()
print(id(cls3) == id(cls4))
使用 類裝飾器實(shí)現(xiàn)單例的原理和 函數(shù)裝飾器 實(shí)現(xiàn)的原理相似,理解了上文,再理解這里應(yīng)該不難。
New、Metaclass 關(guān)鍵字
在接著說另外兩種方法之前,需要了解在 Python 中一個類和一個實(shí)例是通過哪些方法以怎樣的順序被創(chuàng)造的。
簡單來說,元類(metaclass) 可以通過方法 __metaclass__ 創(chuàng)造了類(class),而類(class)通過方法 __new__ 創(chuàng)造了實(shí)例(instance)。
在單例模式應(yīng)用中,在創(chuàng)造類的過程中或者創(chuàng)造實(shí)例的過程中稍加控制達(dá)到最后產(chǎn)生的實(shí)例都是一個對象的目的。
本文主講單例模式,所以對這個 topic 只會點(diǎn)到為止,有感興趣的同學(xué)可以在網(wǎng)上搜索相關(guān)內(nèi)容,幾篇參考文章:
What are metaclasses in Python?
https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python
python-__new__-magic-method-explained
http://howto.lintel.in/python-__new__-magic-method-explained/
Why is __init__() always called after __new__()?
https://stackoverflow.com/questions/674304/why-is-init-always-called-after-new
使用 new 關(guān)鍵字實(shí)現(xiàn)單例模式
使用 __new__ 方法在創(chuàng)造實(shí)例時進(jìn)行干預(yù),達(dá)到實(shí)現(xiàn)單例模式的目的。
class Single(object):
_instance = None
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
def __init__(self):
pass
single1 = Single()
single2 = Single()
print(id(single1) == id(single2))
在理解到 __new__ 的應(yīng)用后,理解單例就不難了,這里使用了
_instance = None
來存放實(shí)例,如果 _instance 為 None,則新建實(shí)例,否則直接返回 _instance 存放的實(shí)例。
使用 metaclass 實(shí)現(xiàn)單例模式
同樣,我們在類的創(chuàng)建時進(jìn)行干預(yù),從而達(dá)到實(shí)現(xiàn)單例的目的。
在實(shí)現(xiàn)單例之前,需要了解使用 type 創(chuàng)造類的方法,代碼如下:
def func(self):
print("do sth")
Klass = type("Klass", (), {"func": func})
c = Klass()
c.func()
以上,我們使用 type 創(chuàng)造了一個類出來。這里的知識是 mataclass 實(shí)現(xiàn)單例的基礎(chǔ)。
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Cls4(metaclass=Singleton):
pass
cls1 = Cls4()
cls2 = Cls4()
print(id(cls1) == id(cls2))
這里,我們將 metaclass 指向 Singleton 類,讓 Singleton 中的 type 來創(chuàng)造新的 Cls4 實(shí)例。
小結(jié)
本文雖然是講單例模式,但在實(shí)現(xiàn)單例模式的過程中,涉及到了蠻多高級 Python 語法,包括裝飾器、元類、new、type 甚至 super 等等。對于新手同學(xué)可能難以理解,其實(shí)在工程項(xiàng)目中并不需要你掌握的面面俱到,掌握其中一種,剩下的作為了解即可。
關(guān)于更多的設(shè)計模式,給初學(xué)者推薦《Head First 設(shè)計模式》(Head First Design Patterns),此書淺顯易懂,在 Head First 系列書籍里面也算是很好的一本。
本文作者:周鑫鑫

本書從 Python 和 Excel 結(jié)合使用的角度講解處理分析數(shù)據(jù)的思路、方法與實(shí)戰(zhàn)應(yīng)用。不論是希望從事數(shù)據(jù)分析崗位的學(xué)習(xí)者,還是其他職業(yè)的辦公人員,都可以通過本書的學(xué)習(xí)掌握 Python 分析數(shù)據(jù)的技能。書中創(chuàng)新性地將 ChatGPT 引入到教學(xué)當(dāng)中,用 ChatGPT 答疑并提供實(shí)訓(xùn)代碼,并介紹了使用 ChatGPT 輔助學(xué)習(xí)的一些實(shí)用技巧,給學(xué)習(xí)者帶來全新的學(xué)習(xí)方式。
公眾號的讀者朋友們購買后可在后臺聯(lián)系我,加入讀者交流群,Crossin會為你開啟陪讀模式,解答你在閱讀本書時的一切疑問。
_往期文章推薦_
