你真的了解Python的單例模式嗎?
標簽: Python Python小站 設計模式 python 多線程
你真的了解Python的單例模式嗎?
最近在用Python單例模式的時候遇到一些問題, 還是自己太年輕了, 在這里總結一下我在使用這個設計模式的時候的坑.
前言(單例模式簡介)
單例模式提供了這樣一個機制,即確保類有且只有一個特定類型的對象,并提供全局訪問點。因此,單例模式通常用于下列情形,例如日志記錄或數據庫操作、打印機后臺處理程序,以及其他程序——該程序運行過程中只能生成一個實例,以避免對同一資源產生相互沖突的請求。例如,我們可能希望使用一個數據庫對象對數據庫進行操作,以維護數據的一致性;或者希望使用一個日志類的對象,將多項服務的日志信息按照順序轉儲到一個特定的日志文件中。
簡單來說, 單例模式可以總結為下面三個要點:
- 確保類有且只有一個對象被創建。
- 為對象提供一個訪問點,以使程序可以全局訪問該對象。
- 控制共享資源的并行訪問。
下文中, 我們將會用Python來實現單例模式, 不做特殊說明, 下文中所有的代碼均基于Python 3.6.8
實現.
經典的單例模式
代碼如下所示:
class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
def test():
s1 = Singleton()
s2 = Singleton()
print(f"s1: {s1}\ns2: {s2}")
我們來運行代碼查看一下運行結果.
可以發現上述代碼滿足如下兩個要求:
- 只允許Singleton類生成一個實例。
- 如果已經有一個實例了,會重復提供同一個對象。
懶漢實例化版單例模式
單例模式的用例之一就是懶漢式實例化。例如,在導入模塊的時候,我們可能會無意中創建一個對象,但當時根本用不到它。懶漢式實例化能夠確保在實際需要時才創建對象。所以,懶漢式實例化是一種節約資源并僅在需要時才創建它們的方式。下面我們來看一下代碼:
class LazySingleton:
__instance = None
def __init__(self):
if not LazySingleton.__instance:
print('Called __init__ method...')
else:
print("Instance already created @: ", self.get_instance())
@classmethod
def get_instance(cls):
if not cls.__instance:
cls.__instance = LazySingleton()
return cls.__instance
def test():
s1 = LazySingleton()
print("Created Object", LazySingleton.get_instance())
s2 = LazySingleton()
同樣來看一下代碼的運行結果,
利用元類實現單例模式
為什么會考慮到這么寫, 因為如果我想對某個類實現單例模式, 我要復制上面的一堆代碼, 這顯現不夠Pythonic, 因此考慮一下使用元類來實現. 有人可能發問了, 這為什么不用繼承來實現, 寫一個基類, 其他子類繼承他不就好了嗎? 悄悄的說一句, 如果用繼承的話是有坑的, 下面首先來看一下繼承方法為什么不可以.
來看一個簡單的例子, 還是使用前面所說的簡單單例.
class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
class FirstChildSingleton(Singleton):
pass
class SecondChildSingleton(Singleton):
pass
def test():
s1 = Singleton()
s2 = Singleton()
print(f"s1: {s1}\ns2: {s2}")
first_child = FirstChildSingleton()
second_child = SecondChildSingleton()
print(f"first child: {first_child}\nsecond child: {second_child}")
打印一下結果, 可以發現,
這里所有的子類也是同一個對象, 這顯然是不行的.
什么是元類
讓我們先來了解一下元類。元類是一個類的類,這意味著該類是它的元類的實例。使用元類,程序員有機會從預定義的Python類創建自己類型的類。
在Python中,一切皆對象。如果我們說a=1
,則type(a)
返回<type'int'>
,這意味著a是int類型。但是,type(int)
返回<type'type'>
,這表明存在一個元類,因為int
是type
類型的類。
具體有關元類的知識, 在本文中不多介紹了, 不是這一篇文章的重點.
單例實現
首先還是老套路, 先來看一下代碼:
class MetaSingleton(type):
def __call__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = super(MetaSingleton, cls).__call__(*args, **kwargs)
return cls._instance
class FirstChildMetaSingleton(metaclass=MetaSingleton):
pass
class SecondChildMetaSingleton(metaclass=MetaSingleton):
pass
def test():
s1 = FirstChildMetaSingleton()
s2 = SecondChildMetaSingleton()
print(f"s1: {s1}\ns2: {s2}")
我們可以發現, 這樣的話, 實例化出來的單例就不是同一個了.
線程安全的單例
你以為寫個元類, 這樣就可以解決一切問題嗎? 哈哈, 太天真了, 上述的寫法實際上是非線程安全的, 我們來看一個例子.
先來看一下代碼,
class SimpleSingleton(metaclass=MetaSingleton):
def __init__(self):
time.sleep(1)
def create_in_thread(name):
s = SimpleSingleton()
print(f'{name}: {s}')
def test():
t1 = threading.Thread(target=create_in_thread, args=("first thread",))
t2 = threading.Thread(target=create_in_thread, args=("second thread",))
t1.start()
t2.start()
t1.join()
t2.join()
從上圖中可以看到, 這樣會創建兩個單例, 也就是說這樣代碼是非線程安全的, 我太難了, 下面來看一個線程安全的單例吧.
class MetaThreadSecuritySingleton(type):
_instance_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
with MetaThreadSecuritySingleton._instance_lock:
if not hasattr(cls, "_instance"):
cls._instance = super(MetaThreadSecuritySingleton, cls).__call__(*args, **kwargs)
return cls._instance
class SimpleThreadSecuritySingleton(metaclass=MetaThreadSecuritySingleton):
def __init__(self):
time.sleep(1)
def create_in_thread(name):
s = SimpleThreadSecuritySingleton()
print(f'{name}: {s}')
def test():
t1 = threading.Thread(target=create_in_thread, args=("first thread",))
t2 = threading.Thread(target=create_in_thread, args=("second thread",))
t1.start()
t2.start()
t1.join()
t2.join()
最終這個單例終于變得線程安全了, 生活中處處充滿著驚喜, 微笑著面對它, 奧利給.
智能推薦
你真的了解Object源碼嗎
歡迎點贊閱讀,一同學習交流,有疑問請留言 。 GitHub上也有開源 JavaHouse 歡迎star 引入 Object 應該是比較簡單的源碼了。現在我們來分析一下他。Object 是類層次結構的根。Java體系里面的每個類默認都有一個超類就是 Object。總之,所有對象,包含數組,都默認實現該類的方法。 native 關鍵字 因為 Object 類里面有很多地方都用到 native 關鍵字。...
你真的了解synchronized嗎?
在多線程并發編程中synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖。但是,隨著Java SE 1.6對synchronized進行了各種優化之后,有些情況下它就并不那么重了。本文詳細介紹java1.6中為了減少 synchronized 獲取鎖和釋放鎖鎖帶來的嚴重的性能消耗而引入的偏向鎖和輕量級鎖,以及鎖膨脹的過程! 一、synchronized實現鎖的表現形式 修飾實例方法,...
@Autowierd &&& @Resource 你真的了解嗎
1.@Autowierd 使用byType注入,默認情況下要求依賴必須存在;配置required=false表示依賴可為null 下面代碼第一個注入是會報錯的,編輯器是會有紅色提示。 因為代碼中沒有這樣的bean,截圖也能看的出來。 2.@Resource 按照ByName自動注入,@Resource有兩個重要的屬性:name和type,而Spring將@Resource注解的 name屬性解析為...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
猜你喜歡
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...