實現單例模式的6種方法(Python)

目錄

一. 基于模塊的實現(簡單,易用)

?二. 重新創建時報錯(不好用)

三. 只靠方法獲取實例(不好用)

四. 類裝飾器

五. 重寫__new__方法

六. 元類

七. 總結


單例模式(Singleton Pattern)是一種設計模式,其核心目標是確保一個類只有一個實例存在,并提供一個全局訪問點。這種模式在需要控制資源訪問,節省系統資源或確保全局一致性的場景中非常有用。下面談一談Python中6種實現單例的方案,復雜程度基本上是由易到難的。除了第一種方案,剩下的在多線程環境中都有風險。如果你需要多線程單例,請注意加鎖!

一. 基于模塊的實現(簡單,易用)

模塊只有在第一次導入時會被初始化,后續導入直接使用已加載的模塊。這讓Python模塊成為了天然的單例,借助它即可輕松獲取一個唯一實例:

class _Singleton:'''一個不開放的單例類'''def __init__(self):self._value = '俺是單例'singleton = _Singleton()

然后,需要用到這個單例的地方直接導入現成的實例:

from module import singletonx = singleton
y = singleton
# x 和 y 是同一個對象嗎?
print(x is y)  # True

像math.pi,math.e等的單例效果就是依此實現的。?

?二. 重新創建時報錯(不好用)

我們可以自己造一個異常來拒絕多次創建實例,當然不造用現成的也可以。比如:

class SingletonError(Exception):'''不能為單例類創建多個實例'''class 孤狼:_instance = Nonedef __init__(self, age):self.age = age# 第一次創建實例時,_instance 為 None,不報錯。# 第二次創建實例時,_instance 不為 None,直接報錯。if self.__class__._instance is not None:raise SingletonError('爺是孤狼,一山不容二虎!')self.__class__._instance = self狼大 = 孤狼(5)
狼二 = 孤狼(4)

在這個世界里,不能存在狼二,更別說光頭弱了:

?不過,其實新的實例已經被創造出來了。只是在初始化的時候強制程序報錯,把這個對象直接“扼殺在搖籃中”了,沒能賦值給“狼二”。而且,這種方法就怕人家把異常捕獲了,那后面會發生什么就不是我們能預測的了。

三. 只靠方法獲取實例(不好用)

在這種方案下,我們必須摒棄傳統的實例創建方法,轉而利用一個類方法獲取實例。

class 孤狼:def __init__(self, age):self.age = age# 必須完全使用這個方法來獲取實例@classmethoddef get_instance(cls, age):# 如果沒有實例化過,就創建一個實例if not hasattr(cls, '_instance'):cls._instance = cls(age)# 如果已經創建過實例,就返回這個實例return cls._instance狼大 = 孤狼.get_instance(5)
狼二 = 孤狼.get_instance(4)
print(狼二.age) # 5,而不是4
print(狼大 is 狼二) # True

這里并沒有真正拒絕像 "孤狼(參數)"?這樣的調用方式,要想完全拒絕這種調用,就繞回第二種方案了。因此這種方法又雞肋又不好用。

你可能會覺得:方案二,三是在搞笑嗎?嗯……這種活兒確實不應該用初級編程方法來干,下面我們看剩下的用元編程技巧實現的三種方案。

四. 類裝飾器

這種方案是用一個工廠函數取代原來的類,直接看實現方式。不過要說明一下,如果要實現單例的類是不可哈希的,就要把使用的鍵從類本身改為類名。不過我沒有這么干,因為單例一般就是不可變的。

from functools import wrapsdef singleton(cls):_instances = {}@wraps(cls)def wrapper(*args, **kwargs):if cls not in _instances:_instances[cls] = cls(*args, **kwargs)return _instances[cls]return wrapper@singleton
class 孤狼:def __init__(self, age):self.age = age
@singleton
class 圓頭耄耋:def __init__(self):self.標志技能 = '哈氣'狼大 = 孤狼(5)
狼二 = 孤狼(4)
print(狼二.age) # 5,而不是4
print(狼大 is 狼二) # True貓爹 = 圓頭耄耋()
貓爺 = 圓頭耄耋()
print(貓爹 is 貓爺) # True

下面解釋一下這個類裝飾器。

from functools import wrapsdef singleton(cls): #(1)_instances = {} #(2)@wraps(cls) #(3)def wrapper(*args, **kwargs): #(4)if cls not in _instances:_instances[cls] = cls(*args, **kwargs)return _instances[cls]return wrapper #(1)

(1):這個類裝飾器以@singleton使用,就相當于編寫了 cls = singleton(cls)。會將返回的內層函數賦值給類,讓類成為內層函數的引用。

(2):這個_instances字典在內層函數的閉包空間內,內層函數可以直接操作它。

(3):就算是單例,使用@wraps保存元數據也是個好習慣!

(4):內層函數現在“奪舍”了類,接受任意參數。如果類不在_instances中,說明還沒有為它創建實例,那就創建一個放到_instances中,最后返回的是_instances中的實例。如果不是首次創建,if條件檢查就不會通過,最終返回的是第一次創建的實例。

也可以給類新填一個類屬性存儲實例,后面元類方案我會展示這兩種不同的實現策略。這應該是三種元編程方案中最好的,__new__不夠靈活,元類太深奧。

五. 重寫__new__方法

__new__方法掌管實例的創建,而不是__init__。更具體地,實例先由__new__創建,然后,如果創建的東西確實是本類的實例,就作為self傳給__init__進行一系列屬性的賦值,完成初始化。如果不是本類的實例(真的可以這樣),就不交給__init__。

不過,實現單例只需要確保每次獲取的是同一個實例即可,不用擔心“生的孩子不是自己的”。下面看具體實現方法:

class 孤狼:_instance = Nonedef __new__(cls, *args, **kwargs):if not cls._instance:cls._instance = super().__new__(cls)return cls._instancedef __init__(self, age):self.age = age狼大 = 孤狼(5)
狼二 = 孤狼(4)
print(狼大.age)  # 4
print(狼二.age)  # 4
print(狼大 is 狼二)  # True

這種使用_instance的技巧我們已經見過多次了,我就不多解釋了。創建實例是委托超類完成的,也就是super().__new__(cls),不需要傳入其他參數——這些參數其實就是__init__那里的參數,只不過“生自家孩子”往往用不到罷了。

那為什么這次反而是狼大的age被狼二覆蓋了?因為屬性age是在__init__中進行賦值的,創建狼二時是最后一次賦值,賦的值是4,所以這個單例的age值從5變成了4。

想要拒絕這種行為,可以在__init__中新增一個條件判斷,這時就又是狼大強壓狼二了:

class 孤狼:_instance = Nonedef __new__(cls, *args, **kwargs):if not cls._instance:cls._instance = super().__new__(cls)return cls._instancedef __init__(self, age):# 如果沒有設置name屬性,則設置它if not hasattr(self, 'age'):self.age = age狼大 = 孤狼(5)
狼二 = 孤狼(4)
print(狼大.age)  # 5
print(狼二.age)  # 5
print(狼大 is 狼二)  # True

使用__new__其實很不靈活,對子類的支持不足——基本必須重寫子類的__new__方法。相比而言,類裝飾器和元類就能輕松支持任何類。

六. 元類

一般地,有其他方案我們就不會動用元類,一切類都是元類的實例,它是Python的“終極武器”和“黑魔法”。一來元類相對而言太高深了,二來元類的接口不一定就比其他方案好使。我就覺得類裝飾器超級好用呀!如果要在三種元編程方案中選一個,我肯定會選類裝飾器。

元類強大到可以干涉類的創建,初始化,和實例化三個過程。分別依賴元類的__new__,__init__,和__call__方法。現在我們想要插手實例創建的邏輯,應該在__call__上下功夫。

你會發現,下面的元類方案和類裝飾器方案很類似,都是在外部存儲了一個字典:

class MetaSingleton(type):_instances = {}def __call__(cls, *args, **kwargs):if cls not in MetaSingleton._instances:MetaSingleton._instances[cls] = super().__call__(*args, **kwargs)return MetaSingleton._instances[cls]class 孤狼(metaclass=MetaSingleton):def __init__(self, age):self.age = age狼大 = 孤狼(5)
狼二 = 孤狼(4)
print(狼大.age) # 5
print(狼二.age) # 5
print(狼大 is 狼二) # True

只不過,元類中的實例要委托超類type的__call__來創建,也就是super().__call__(*args, **kwargs)。使用元類的話,在定義類時指定metaclass=……就好了。

也可以把單例存儲在類屬性中,像下面這樣:

class MetaSingleton(type):def __call__(cls, *args, **kwargs):if not hasattr(cls, '_instances'):cls._instances = Noneif cls not in cls._instances:cls._instances = super().__call__(*args, **kwargs)return cls._instances

第一種方式缺點是占用的空間可能更大,而第二種方式缺點是給類新添了一個屬性,在做元編程時可能導致意外發生。?

七. 總結

推薦使用模塊單例類裝飾器,方案二,三就是來搞笑的,剩下的兩種方案的話——__new__更復雜且不夠靈活好用;動用元類實現單例完全沒必要。只有非常少數的情況是非用元類不可的,我們對元類的態度往往是能不用就不用。如果你感興趣,我這里有一個真正需要元類出馬的簡單案例:

利用元類優化裝飾器接口的方案https://blog.csdn.net/2402_85728830/article/details/148046472

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/82925.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/82925.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/82925.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

循環神經網絡(RNN)全面教程:從原理到實踐

循環神經網絡(RNN)全面教程:從原理到實踐 引言 循環神經網絡(Recurrent Neural Network, RNN)是處理序列數據的經典神經網絡架構,在自然語言處理、語音識別、時間序列預測等領域有著廣泛應用。本文將系統介紹RNN的核心概念、常見變體、實現方法以及實際…

使用Vditor將Markdown文檔渲染成網頁(Vite+JS+Vditor)

1. 引言 編寫Markdown文檔現在可以說是程序員的必備技能了,因為Markdown很好地實現了內容與排版分離,可以讓程序員更專注于內容的創作。現在很多技術文檔,博客發布甚至AI文字輸出的內容都是以Markdown格式的形式輸出的。那么,Mar…

Day 40

單通道圖片的規范寫法 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader , Dataset from torchvision import datasets, transforms import matplotlib.pyplot as plt import warnings warnings.filterwarnings(&q…

SPSS跨域分類:自監督知識+軟模板優化

1. 圖1:SPSS方法流程圖 作用:展示了SPSS方法的整體流程,從數據預處理到模型預測的關鍵步驟。核心內容: 領域知識提取:使用三種詞性標注工具(NLTK、spaCy、TextBlob)從源域和目標域提取名詞或形容詞(如例句中提取“excellent”“good”等形容詞)。詞匯交集與聚類:對提…

2025年通用 Linux 服務器操作系統該如何選擇?

2025年通用 Linux 服務器操作系統該如何選擇? 服務器操作系統的選擇對一個企業IT和云服務影響很大,主推的操作系統在后期更換的成本很高,而且也有很大的遷移風險,所以企業在選擇服務器操作系統時要尤為重視。 之前最流行的服務器…

如何在 Django 中集成 MCP Server

目錄 背景說明第一步:使用 ASGI第二步:修改 asgi.py 中的應用第三步:Django 數據的異步查詢 背景說明 有幾個原因導致 Django 集成 MCP Server 比較麻煩 目前支持的 MCP 服務是 SSE 協議的,需要長連接,但一般來講 Dj…

天拓四方工業互聯網平臺賦能:地鐵電力配電室綜合監控與無人巡檢,實現效益與影響的雙重顯著提升

隨著城市化進程的不斷加快,城市軌道交通作為緩解交通壓力、提升出行效率的重要方式,在全國各大城市中得到了迅猛發展。地鐵電力配電室作為核心供電設施,其基礎設施的安全性、穩定性和智能化水平也面臨更高要求。 本文將圍繞“工業物聯網平臺…

算法打卡第11天

36.有效的括號 (力扣20題) 示例 1: **輸入:**s “()” **輸出:**true 示例 2: **輸入:**s “()[]{}” **輸出:**true 示例 3: **輸入:**s “(]”…

python 包管理工具uv

uv --version uv python find uv python list export UV_DEFAULT_INDEX"https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" # 換成私有的repo export UV_HTTP_TIMEOUT120 uv python install 3.12 uv venv myenv --python 3.12 --seed uvhttps://docs.ast…

spring的多語言怎么實現?

1.創建springboot項目,并配置application.properties文件 spring.messages.basenamemessages spring.messages.encodingUTF-8 spring.messages.fallback-to-system-localefalsespring.thymeleaf.cachefalse spring.thymeleaf.prefixclasspath:/templates/ spring.t…

JAVA:Kafka 消息可靠性詳解與實踐樣例

?? 1、簡述 Apache Kafka 是高吞吐、可擴展的流處理平臺,在分布式架構中廣泛應用于日志采集、事件驅動和微服務解耦場景。但在使用過程中,消息是否會丟?何時丟?如何防止丟? 是很多開發者關心的問題。 Kafka 提供了一套完整的機制來保障消息從生產者 ? Broker ? 消費…

【AI非常道】二零二五年五月,AI非常道

經常在社區看到一些非常有啟發或者有收獲的話語,但是,往往看過就成為過眼云煙,有時再想去找又找不到。索性,今年開始,看到好的言語,就記錄下來,一月一發布,亦供大家參考。 前面的記…

C++哈希

一.哈希概念 哈希又叫做散列。本質就是通過哈希函數把關鍵字key和存儲位置建立映射關系,查找時通過這個哈希函數計算出key存儲的位置,進行快速查找。 上述概念可能不那么好懂,下面的例子可以輔助我們理解。 無論是數組還是鏈表,查…

iOS 使用CocoaPods 添加Alamofire 提示錯誤的問題

Sandbox: rsync(59817) deny(1) file-write-create /Users/aaa/Library/Developer/Xcode/DerivedData/myApp-bpwnzikesjzmbadkbokxllvexrrl/Build/Products/Debug-iphoneos/myApp.app/Frameworks/Alamofire.framework/Alamofire.bundle把這個改成 no 2 設置配置文件

mysql的Memory引擎的深入了解

目錄 1、Memory引擎介紹 2、Memory內存結構 3、內存表的鎖 4、持久化 5、優缺點 6、應用 前言 Memory 存儲引擎 是 MySQL 中一種高性能但非持久化的存儲方案,適合臨時數據存儲和緩存場景。其核心優勢在于極快的讀寫速度,需注意數據丟失風險和內存占…

若依項目AI 助手代碼解析

基于 Vue.js 和 Element UI 的 AI 助手組件 一、組件整體結構 這個 AI 助手組件由三部分組成&#xff1a; 懸浮按鈕&#xff1a;點擊后展開 / 收起對話窗口對話窗口&#xff1a;顯示歷史消息和輸入框API 調用邏輯&#xff1a;與 AI 服務通信并處理響應 <template><…

Vue2的diff算法

diff算法的目的是為了找出需要更新的節點&#xff0c;而未變化的節點則可以復用 新舊列表的頭尾先互相比較。未找到可復用則開始遍歷&#xff0c;對比過程中指針逐漸向列表中間靠攏&#xff0c;直到遍歷完其中一個列表 具體策略如下&#xff1a; 同層級比較 Vue2的diff算法只…

mongodb集群之分片集群

目錄 1. 適用場景2. 集群搭建如何搭建搭建實例Linux搭建實例(待定)Windows搭建實例1.資源規劃2. 配置conf文件3. 按順序啟動不同角色的mongodb實例4. 初始化config、shard集群信息5. 通過router進行分片配置 1. 適用場景 數據量大影響性能 數據量大概達到千萬級或億級的時候&…

DEEPSEEK幫寫的STM32消息流函數,直接可用.已經測試

#include "main.h" #include "MessageBuffer.h"static RingBuffer msgQueue {0};// 初始化隊列 void InitQueue(void) {msgQueue.head 0;msgQueue.tail 0;msgQueue.count 0; }// 檢查隊列狀態 type_usart_queue_status GetQueueStatus(void) {if (msgQ…

華為歐拉系統中部署FTP服務與Filestash應用:實現高效文件管理和共享

華為歐拉系統中部署FTP服務與Filestash應用:實現高效文件管理和共享 前言一、相關服務介紹1.1 Huawei Cloud EulerOS介紹1.2 Filestash介紹1.3 華為云Flexus應用服務器L實例介紹二、本次實踐介紹2.1 本次實踐介紹2.2 本次環境規劃三、檢查云服務器環境3.1 登錄華為云3.2 SSH遠…