Python 單例模式的幾種實(shí)現(xiàn)方式
前言
最近在整理代碼時(shí),重新審視了項(xiàng)目中用到的單例模式實(shí)現(xiàn)。發(fā)現(xiàn)自己對(duì)單例模式的理解經(jīng)歷了一個(gè)有趣的循環(huán):從懵懂使用,到追求各種"巧妙"實(shí)現(xiàn),再到回歸樸素。
今天想分享幾種 Python 實(shí)現(xiàn)單例模式的方式,并談?wù)勎叶嗄昃幊痰囊粋€(gè)感悟:樸素的就是最好的。
一、單例模式簡介
單例模式(Singleton Pattern)是一種設(shè)計(jì)模式,確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。
?? 適用場景
| 場景 | 說明 |
|---|---|
| 數(shù)據(jù)庫連接 | 全局共享一個(gè)連接池 |
| 日志記錄器 | 避免重復(fù)打開文件 |
| 配置管理器 | 全局只讀一份配置 |
| 緩存實(shí)例 | 全局共享緩存 |
二、四種實(shí)現(xiàn)方式
方式一:類裝飾器
def singleton(cls):
"""單例裝飾器:閉包保存唯一實(shí)例"""
_instance = None
def wrapper(*args, **kwargs):
nonlocal _instance
if _instance is None:
_instance = cls(*args, **kwargs)
return _instance
return wrapper
@singleton
class DBConnect:
def __init__(self, host):
self.host = host
print("數(shù)據(jù)庫連接初始化")
# 使用
obj1 = DBConnect("127.0.0.1")
obj2 = DBConnect("192.168.1.1") # 不會(huì)重新初始化
print(obj1 is obj2) # True
print(obj1.host) # 127.0.0.1
特點(diǎn):裝飾器語法優(yōu)雅,使用時(shí)一目了然。
方式二:__new__方法
class MySingleton:
"""單例類:通過 __new__ 控制實(shí)例創(chuàng)建"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# 使用
obj1 = MySingleton()
obj2 = MySingleton()
print(obj1 is obj2) # True
特點(diǎn):最直接的方式,但 _instance 作為類變量暴露在外。我之前的博文 Playwright理解與封裝就是使用這種方式,感覺不太好,準(zhǔn)備重構(gòu)一下,那個(gè)類根本不需要單例模式,如果使用者覺得有必要,可以顯式自行單例化即可。
方式三:函數(shù)
from typing import Type, TypeVar
T = TypeVar('T')
def singleton(cls: Type[T], *args, **kwargs) -> T:
"""單例工廠函數(shù)"""
if not hasattr(cls, '_instance'):
cls._instance = cls(*args, **kwargs)
return cls._instance
# 使用
class TargetClass:
def __init__(self, config):
self.config = config
obj = singleton(TargetClass, config="value")
特點(diǎn):需要顯式調(diào)用 singleton(),語義明確但稍顯繁瑣。
方式四:functools.lru_cache
from functools import lru_cache
@lru_cache(maxsize=1)
def get_instance():
return MyClass(arg1="value")
# 使用
obj1 = get_instance()
obj2 = get_instance()
print(obj1 is obj2) # True
特點(diǎn):Python 內(nèi)置,簡單直接,還能享受緩存的好處。
三、優(yōu)缺點(diǎn)對(duì)比
| 實(shí)現(xiàn)方式 | 優(yōu)點(diǎn) | 缺點(diǎn) | 推薦指數(shù) |
|---|---|---|---|
| 類裝飾器 | 語法優(yōu)雅、語義清晰、易于理解 | 對(duì)于非必要實(shí)現(xiàn)單例的類,對(duì)使用者會(huì)造成困擾 | ???? |
| new 方法 | 最直觀、最簡單 | 實(shí)例變量暴露、可復(fù)用性不好 | ?? |
| 單例函數(shù) | 顯式調(diào)用、可控性強(qiáng),使用者清楚知道正在做什么 | 每次都要調(diào)用、不夠直觀 | ??? |
| lru_cache | 內(nèi)置、無需額外代碼 | 語義上不是單例、更像緩存,但是勝在簡單 | ???? |
四、我的建議
?? 優(yōu)先使用顯式單例
相比把類變成單例,顯式獲取單例的寫法更加清晰:
# ? 推薦:顯式獲取單例
_db = None
def get_db():
global _db
if _db is None:
_db = Database()
return _db
# 或者更簡潔地用 lru_cache
@lru_cache
def get_db():
return Database()
# ? 不推薦:把類變成單例,除非很確定此類必須是單例類,不會(huì)造成使用者誤解
@singleton
class Database:
...
為什么?
- 透明性:使用者能清楚看到這個(gè)類是全局共享的
- 可控性:可以靈活控制何時(shí)創(chuàng)建、如何創(chuàng)建
- 易測試:mock 和替換更容易
- 少歧義:不會(huì)讓使用者誤以為每次
new都是新實(shí)例
五、多年編程感悟
樸素的就是最好的。所有當(dāng)時(shí)自鳴得意的所謂編程技巧,最后都會(huì)被證明要用更多的代價(jià)來理解和維護(hù)它。
?? 幾個(gè)教訓(xùn)
| 當(dāng)年覺得"巧妙" | 后來發(fā)現(xiàn)的問題 |
|---|---|
| 復(fù)雜的繼承鏈 | 調(diào)試時(shí)一頭霧水 |
| 動(dòng)態(tài)修改類屬性 | 難以追蹤狀態(tài)變化 |
| 魔法裝飾器 | 新人看不懂 |
| 元編程炫技 | 給自己挖坑 |
?? 回歸樸素
# 曾經(jīng)的我:要用單例模式!
@singleton
class ConfigManager:
...
# 現(xiàn)在的我:簡單就好
@lru_cache
def get_db():
return Database()
簡單、直白、不需要解釋。這才是好的代碼。
六、總結(jié)
?? 要點(diǎn)回顧
- 裝飾器方式是最推薦的類單例實(shí)現(xiàn)(針對(duì)必須實(shí)現(xiàn)單例的類)
- 顯式獲取單例比把類變成單例更好
- lru_cache 是最簡單的單例緩存方案
- 樸素思維:能用簡單方式解決的問題,不要過度設(shè)計(jì)
到此這篇關(guān)于Python 單例模式的幾種實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)Python 單例模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python實(shí)現(xiàn)將兩個(gè)文件夾合并至另一個(gè)文件夾(制作數(shù)據(jù)集)
這篇文章主要介紹了python實(shí)現(xiàn)將兩個(gè)文件夾合并至另一個(gè)文件夾(制作數(shù)據(jù)集),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04
在Python的Django框架中用流響應(yīng)生成CSV文件的教程
這篇文章主要介紹了在Python的Django框架中用流響應(yīng)生成CSV文件的教程,作者特別講到了防止CSV文件中的中文避免出現(xiàn)亂碼等問題,需要的朋友可以參考下2015-05-05
Python反爬機(jī)制-驗(yàn)證碼功能的具體實(shí)現(xiàn)過程
Tesseract-OCR是一個(gè)免費(fèi)、開源的OCR引擎,通過該引擎可以識(shí)別圖片中的驗(yàn)證碼,這篇文章主要介紹了Python反爬機(jī)制-驗(yàn)證碼的示例代碼,需要的朋友可以參考下2022-02-02
Python如何通過ARIMA模型進(jìn)行時(shí)間序列分析預(yù)測
這篇文章主要介紹了Python如何通過ARIMA模型進(jìn)行時(shí)間序列分析預(yù)測問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02
Python 實(shí)現(xiàn)Excel XLS和XLSX格式相互轉(zhuǎn)換問題
本文介紹如何使用Python庫Spire.XLS for Python實(shí)現(xiàn)Excel文件的XLS和XLSX格式轉(zhuǎn)換,提供了詳細(xì)的安裝指南和轉(zhuǎn)換步驟,幫助用戶在不同版本的Excel文件格式之間靈活轉(zhuǎn)換,同時(shí)支持將Excel文件轉(zhuǎn)換為PDF、圖片、HTML等格式2024-10-10
python flask基于cookie和session來實(shí)現(xiàn)會(huì)話控制的實(shí)戰(zhàn)代碼
所謂的會(huì)話(session),就是客戶端瀏覽器和服務(wù)端網(wǎng)站之間一次完整的交互過程,本文介紹falsk通過cookie和session來控制http會(huì)話的全部解析,通常我們可以用cookie和session來保持用戶登錄等,感興趣的朋友一起看看吧2024-03-03
python新手學(xué)習(xí)可變和不可變對(duì)象
在本篇文章里小編給大家分享了是一篇關(guān)于python可變對(duì)象和不可變對(duì)象的基礎(chǔ)知識(shí)點(diǎn)內(nèi)容,有需要的朋友們可以參考下。2020-06-06

