python緩存裝飾器實現方案

寫python的時候突然想著能不能用注解于是就寫了個這個


文章目錄

  • 原始版
  • 改進點

原始版

import os
import pickle
import hashlib
import inspect
import functoolsdef _generate_cache_filename(func, *args, **kwargs):"""生成緩存文件名的內部函數"""# 獲取調用來源文件的絕對路徑caller_frame = inspect.stack()[2]  # 注意調整為2,跳過當前函數和調用者caller_file = os.path.abspath(caller_frame.filename)# 生成調用文件路徑的短哈希file_hash = hashlib.md5(caller_file.encode()).hexdigest()[:8]# 生成參數簽名args_repr = "_".join([repr(arg) for arg in args])kwargs_repr = "_".join([f"{k}={repr(v)}" for k, v in kwargs.items()])# 處理無參數情況param_repr = f"{args_repr}_{kwargs_repr}" if (args or kwargs) else "no_params"# 組合最終緩存文件名return f"{func.__name__}_{param_repr}_{file_hash}.pkl"def cache(func):@functools.wraps(func)def wrapper(*args, **kwargs):# 使用共享函數生成緩存文件名cache_file = _generate_cache_filename(func, *args, **kwargs)# 緩存邏輯if os.path.exists(cache_file):with open(cache_file, 'rb') as f:return pickle.load(f)result = func(*args, **kwargs)with open(cache_file, 'wb') as f:pickle.dump(result, f)print(f'緩存已保存:{cache_file}')return resultreturn wrapperdef clear_cache(func, *args, **kwargs):"""手動清除緩存文件"""# 使用共享函數生成緩存文件名cache_file = _generate_cache_filename(func, *args, **kwargs)# 刪除緩存文件if os.path.exists(cache_file):os.remove(cache_file)print(f"緩存已刪除: {cache_file}")else:print(f"緩存文件不存在: {cache_file}")# 測試用例
@cache
def get_data(a, b):print("計算數據")return a + bif __name__ == "__main__":# 第一次調用(創建緩存)print(get_data(1, 2))  # 輸出: 計算數據 和 3# 第二次調用(讀取緩存)print(get_data(1, 2))  # 無"計算數據"輸出# 清除緩存clear_cache(get_data, 1, 2)  # 成功刪除# 再次調用(重新計算)print(get_data(1, 2))  # 再次輸出"計算數據"

1._generate_cache_filename用于生成緩存文件名字,inspect.stack()[2]獲取調用棧中的當前使用的文件名字,提取調用文件的絕對路徑并轉換為8位MD5哈希值。

*args和**kwargs分別轉換為字符串表示,用于區分不同參數的同名函數,當函數無參數時,使用"no_params"。
【這里需要todo一下:傳入的參數判斷是否能做為合法的文件名字】

最終生成"函數名_參數簽名_調用文件哈希.pkl"。
【todo:最終的文件名稱不能超過系統保存的最大長度】

需要確保_generate_cache_filename函數能生成唯一且合法的文件名

2.def cache(func)
簡單的緩存裝飾器,將函數的計算結果持久化到文件中
使用裝飾器模式,外層函數接受被裝飾函數作為參數

functools.wraps保留原函數的元信息
內層wrapper函數處理實際調用邏輯
通過_generate_cache_filename函數生成唯一的緩存文件名

檢查緩存文件是否存在,存在則直接讀取并返回緩存結果

否則調用原始函數獲取計算結果,使用pickle模塊序列化結果到文件,打印緩存保存信息,返回計算結果

注意:
被緩存函數的返回值必須可被pickle序列化
在多進程環境中使用時需注意文件鎖問題
緩存文件需要定期清理以避免存儲空間占用

需要todo改進:
添加緩存過期機制
支持自定義序列化方法 todo
添加緩存命中率統計
支持分布式緩存存儲 todo

3.def clear_cache(func, *args, **kwargs)
用于手動清除特定函數的緩存文件。
檢查緩存文件是否存在,若存在則刪除并打印確認信息;若不存在則提示文件不存在的狀態。

文件刪除操作不可逆,需謹慎調用。

改進點

1、合法文件名處理:

使用正則表達式移除非法字符:re.sub(r’[<>:"/\|?*\x00-\x1F]', ‘_’, name)

處理特殊字符和不可打印字符

2、文件名長度截斷:

限制文件名最大長度(255字符)

對長文件名進行智能截斷(保留首尾部分)

3、緩存過期機制:

添加expire_seconds參數控制緩存有效期

基于文件修改時間檢查過期狀態

默認過期時間為24小時

4、日志系統:

使用Python標準logging模塊

不同級別的日志(DEBUG、INFO、WARNING、ERROR)

格式化的日志輸出

5、異常處理:

捕獲并記錄文件操作中的所有異常

提供有意義的錯誤信息

緩存失敗時不影響主程序運行

6、自定義緩存目錄:

可配置的緩存目錄參數

自動創建不存在的目錄

默認目錄為./.cache

7、緩存統計:

跟蹤命中、未命中和過期次數

計算命中率

線程安全的統計計數器

按函數名查看統計信息

7、多線程/進程安全:

使用filelock庫實現跨進程文件鎖

為每個緩存文件創建對應的鎖文件

設置鎖超時時間(10秒)

8、增強的緩存清除:

清除特定參數的緩存

清除函數的所有緩存

批量刪除操作

9、附加功能:

添加了清理緩存的方法(clear_cache和clear_all_cache)

統計信息查看函數(get_cache_stats和print_cache_stats)

智能緩存路徑管理

import os
import pickle
import hashlib
import inspect
import functools
import time
import re
import logging
import threading
from collections import defaultdict
from filelock import FileLock# 設置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger('cache_decorator')# 緩存統計
cache_stats = defaultdict(lambda: {'hits': 0, 'misses': 0, 'expired': 0, 'deleted': 0})
stats_lock = threading.Lock()# 默認配置
DEFAULT_CACHE_DIR = os.path.join(os.getcwd(), '.cache')
DEFAULT_EXPIRE_SECONDS = 24 * 60 * 60  # 默認過期時間: 24小時
MAX_FILENAME_LENGTH = 200  # 最大文件名長度def _sanitize_filename(name):"""移除文件名中的非法字符并截斷長度"""# 替換非法字符sanitized = re.sub(r'[<>:"/\\|?*\x00-\x1F]', '_', name)# 截斷文件名if len(sanitized) > MAX_FILENAME_LENGTH:prefix = sanitized[:MAX_FILENAME_LENGTH // 2]suffix = sanitized[-MAX_FILENAME_LENGTH // 2:]sanitized = prefix + '...' + suffix# 確保最終長度不超過限制sanitized = sanitized[:MAX_FILENAME_LENGTH]return sanitizeddef _generate_cache_filename(func, *args, **kwargs):"""生成緩存文件名的內部函數"""# 獲取調用來源文件的絕對路徑caller_frame = inspect.stack()[2]  # 調整堆棧深度caller_file = os.path.abspath(caller_frame.filename)# 生成調用文件路徑的短哈希file_hash = hashlib.md5(caller_file.encode()).hexdigest()[:8]# 生成參數簽名args_repr = "_".join([repr(arg) for arg in args])kwargs_repr = "_".join([f"{k}={repr(v)}" for k, v in sorted(kwargs.items())])# 處理無參數情況param_repr = f"{args_repr}_{kwargs_repr}" if (args or kwargs) else "no_params"# 組合并清理文件名raw_filename = f"{func.__name__}_{param_repr}_{file_hash}"return _sanitize_filename(raw_filename) + ".pkl"def _get_cache_file_path(cache_dir, cache_file):"""獲取緩存文件完整路徑,確保目錄存在"""# 創建緩存目錄(如果不存在)os.makedirs(cache_dir, exist_ok=True)return os.path.join(cache_dir, cache_file)def cache(expire_seconds=DEFAULT_EXPIRE_SECONDS, cache_dir=DEFAULT_CACHE_DIR):"""帶參數的緩存裝飾器Args:expire_seconds (int): 緩存過期時間(秒)cache_dir (str): 緩存文件存儲目錄"""def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):# 生成緩存文件名cache_file = _generate_cache_filename(func, *args, **kwargs)cache_path = _get_cache_file_path(cache_dir, cache_file)lock_path = cache_path + ".lock"# 使用文件鎖確保線程/進程安全with FileLock(lock_path, timeout=10):# 檢查緩存是否存在且未過期if os.path.exists(cache_path):file_age = time.time() - os.path.getmtime(cache_path)if expire_seconds is None or file_age < expire_seconds:# 緩存命中try:with open(cache_path, 'rb') as f:result = pickle.load(f)with stats_lock:cache_stats[func.__name__]['hits'] += 1logger.debug(f'緩存命中: {cache_path}')return resultexcept Exception as e:logger.warning(f'加載緩存失敗: {e}')# 緩存過期with stats_lock:cache_stats[func.__name__]['expired'] += 1logger.debug(f'緩存已過期: {cache_path}')# 緩存未命中或過期,重新計算with stats_lock:cache_stats[func.__name__]['misses'] += 1result = func(*args, **kwargs)# 保存結果到緩存try:with open(cache_path, 'wb') as f:pickle.dump(result, f)logger.debug(f'緩存已保存: {cache_path}')except Exception as e:logger.error(f'保存緩存失敗: {e}')return result# 為包裝的函數添加清除緩存的方法def clear_cache(*args, **kwargs):"""清除特定參數的緩存"""cache_file = _generate_cache_filename(func, *args, **kwargs)cache_path = _get_cache_file_path(cache_dir, cache_file)if os.path.exists(cache_path):try:os.remove(cache_path)logger.info(f'緩存已刪除: {cache_path}')with stats_lock:cache_stats[func.__name__]['deleted'] += 1return Trueexcept Exception as e:logger.error(f'刪除緩存失敗: {e}')return Falseelse:logger.warning(f'緩存文件不存在: {cache_path}')return Falsedef clear_all_cache():"""清除該函數的所有緩存"""pattern = re.compile(f"^{func.__name__}_.*\\.pkl$")cleared = 0total = 0for filename in os.listdir(cache_dir):if pattern.match(filename):total += 1file_path = os.path.join(cache_dir, filename)try:os.remove(file_path)cleared += 1with stats_lock:cache_stats[func.__name__]['deleted'] += 1except Exception as e:logger.error(f'刪除緩存失敗 {filename}: {e}')logger.info(f'已清除 {cleared}/{total} 個緩存文件')return cleareddef clear_expired_cache(expire_seconds=expire_seconds):"""清除該函數的所有過期緩存"""pattern = re.compile(f"^{func.__name__}_.*\\.pkl$")current_time = time.time()removed = 0total = 0for filename in os.listdir(cache_dir):if pattern.match(filename):total += 1file_path = os.path.join(cache_dir, filename)try:# 檢查文件是否過期mtime = os.path.getmtime(file_path)if current_time - mtime > expire_seconds:os.remove(file_path)removed += 1with stats_lock:cache_stats[func.__name__]['deleted'] += 1logger.debug(f'已刪除過期緩存: {filename}')except Exception as e:logger.error(f'處理緩存文件 {filename} 失敗: {e}')logger.info(f'已刪除 {removed}/{total} 個過期緩存文件')return removedwrapper.clear_cache = clear_cachewrapper.clear_all_cache = clear_all_cachewrapper.clear_expired_cache = clear_expired_cachereturn wrapperreturn decoratordef get_cache_stats(func_name=None):"""獲取緩存統計信息Args:func_name (str): 函數名,None 表示所有函數Returns:dict: 緩存統計信息"""with stats_lock:if func_name:return cache_stats.get(func_name, {'hits': 0, 'misses': 0, 'expired': 0, 'deleted': 0})# 計算總命中率total_stats = {'hits': 0, 'misses': 0, 'expired': 0, 'deleted': 0}for stats in cache_stats.values():for k in total_stats:total_stats[k] += stats[k]# 添加命中率百分比total = total_stats['hits'] + total_stats['misses'] + total_stats['expired']if total > 0:total_stats['hit_rate'] = total_stats['hits'] / total * 100else:total_stats['hit_rate'] = 0.0return total_statsdef print_cache_stats(func_name=None):"""打印緩存統計信息"""stats = get_cache_stats(func_name)if func_name:print(f"\n緩存統計 - {func_name}:")else:print("\n全局緩存統計:")print(f"命中次數: {stats['hits']}")print(f"未命中次數: {stats['misses']}")print(f"過期次數: {stats['expired']}")print(f"刪除次數: {stats['deleted']}")if 'hit_rate' in stats:print(f"命中率: {stats['hit_rate']:.2f}%")else:total = stats['hits'] + stats['misses'] + stats['expired']if total > 0:hit_rate = stats['hits'] / total * 100print(f"命中率: {hit_rate:.2f}%")def clear_all_expired_cache(cache_dir=DEFAULT_CACHE_DIR, expire_seconds=DEFAULT_EXPIRE_SECONDS):"""清除緩存目錄中所有過期的緩存文件Args:cache_dir (str): 緩存目錄expire_seconds (int): 過期時間(秒)"""current_time = time.time()removed = 0total = 0if not os.path.exists(cache_dir):logger.warning(f"緩存目錄不存在: {cache_dir}")return 0for filename in os.listdir(cache_dir):if filename.endswith('.pkl'):total += 1file_path = os.path.join(cache_dir, filename)try:# 檢查文件是否過期mtime = os.path.getmtime(file_path)if current_time - mtime > expire_seconds:os.remove(file_path)removed += 1with stats_lock:# 嘗試找出對應的函數名func_name = filename.split('_')[0]if func_name in cache_stats:cache_stats[func_name]['deleted'] += 1logger.debug(f'已刪除過期緩存: {filename}')except Exception as e:logger.error(f'處理緩存文件 {filename} 失敗: {e}')logger.info(f'已刪除 {removed}/{total} 個過期緩存文件')return removed# 測試用例
@cache(expire_seconds=2, cache_dir="./test_cache")
def get_data(a, b):print("計算數據")return a + bif __name__ == "__main__":# 確保測試緩存目錄存在os.makedirs("./test_cache", exist_ok=True)# 第一次調用(創建緩存)print(get_data(1, 2))  # 輸出: 計算數據 和 3# 第二次調用(讀取緩存)print(get_data(1, 2))  # 無"計算數據"輸出# 等待緩存過期time.sleep(3)# 第三次調用(緩存過期后重新計算)print(get_data(1, 2))  # 再次輸出"計算數據"# 清除特定參數緩存get_data.clear_cache(1, 2)# 第四次調用(清除后重新計算)print(get_data(1, 2))  # 輸出"計算數據"# 創建另一個緩存print(get_data(3, 4))# 等待緩存過期time.sleep(3)# 清除過期緩存(僅限get_data函數)get_data.clear_expired_cache()# 清除整個緩存目錄中的過期緩存clear_all_expired_cache("./test_cache", expire_seconds=1)# 清除所有緩存get_data.clear_all_cache()# 打印緩存統計print_cache_stats()

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

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

相關文章

使用 java -jar xxxx.jar 運行 jar 包報錯: no main manifest attribute

1、問題描述 在Linux服務器上本想運行一下自己寫的一個JAR&#xff0c;但是報錯了&#xff01; no main manifest attribute, in first-real-server-1.0-SNAPSHOT.jar 2、解決辦法 在自己的Spring項目的啟動類&#xff08;xxx.xxx.xxx.XXXXApplication&#xff09;所在的Mo…

信號與槽的總結

信號與槽的總結 QT中的信號與Linux的信號對比 1&#xff09;信號源 2&#xff09;信號的類型 3&#xff09;信號的處理方式 QT信號與Linux信號的深度對比分析 一、信號源對比 QT信號 用戶定義信號 &#xff1a;由開發者通過 signals:關鍵字在QObject派生類中顯式聲明 cl…

Python Mitmproxy詳解:從入門到實戰

一、Mitmproxy簡介 Mitmproxy是一款開源的交互式HTTPS代理工具&#xff0c;支持攔截、修改和重放HTTP/HTTPS流量。其核心優勢在于&#xff1a; 多平臺支持&#xff1a;兼容Windows、macOS、Linux三端工具&#xff1a;提供命令行(mitmproxy)、Web界面(mitmweb)、數據流處理(mi…

刷題筆記--串聯所有單詞的子串

題目&#xff1a;1、我的寫法&#xff08;超時&#xff09;從題面自然想到先用回溯算法把words的全排列先算出來&#xff0c;然后遍歷字符串s一次將符合條件的位置加入結果全排列計算所有可能字符串算法寫法&#xff1a;這是一個模板用于所有全排列算法的情況&#xff0c;本質思…

操作系統【1】【硬件結構】【操作系統結構】

一、CPU如何執行程序&#xff1f; 提綱 圖靈機工作方式馮諾依曼模型線路位寬CPU位寬程序執行基本過程執行具體過程 1. 圖靈機工作方式 圖靈機可以視作“一臺帶規則的自動草稿機” 圖靈機基本組成&#xff1a; 紙帶&#xff08;內存&#xff09;&#xff1a;連續格子組成&…

SQLite與MySQL:嵌入式與客戶端-服務器數據庫的權衡

SQLite與MySQL&#xff1a;嵌入式與客戶端-服務器數據庫的權衡 在開發應用程序時&#xff0c;數據庫選擇是一個至關重要的決策&#xff0c;它會影響應用的性能、可擴展性、部署難度和維護成本。SQLite和MySQL是兩種廣泛使用的關系型數據庫管理系統&#xff0c;它們各自針對不同…

CppCon 2018 學習:Smart References

“強類型別名”&#xff08;strong typedefs&#xff09; 的動機和實現&#xff0c;配合一個簡單例子說明&#xff1a; 動機&#xff08;Motivation&#xff09; 用 using filename_t string; 和 using url_t string; 來區分不同的字符串類型&#xff08;比如文件名和網址&…

高性能高準確度的CPU電壓與溫度監測軟件HWInfo

&#x1f5a5;? 一、軟件概述 Windows版&#xff1a;圖形化界面&#xff0c;支持實時監控&#xff08;溫度、電壓、風扇轉速等&#xff09;、基準測試及報告生成&#xff0c;兼容Windows XP至Windows 11系統。Linux版&#xff1a;命令行工具&#xff0c;由openSUSE社區維護&a…

H3C WA6322 AP版本升級

1、查看當前版本&#xff1a;R2444P01 2、官網下載升級文件&#xff1a; WA6300系列版本說明H3C WA6300系列(適用于WA6330、 WA6322、WA6320H、WA6320、 WTU630H、WTU630、WA6330-LI、WA6320-C、WA6320-D、WA6320H-LI、WA6338、WA6322H、WTU632H-IOT、WAP922E、WAP923、WA6320…

用 YOLOv8 + DeepSORT 實現目標檢測、追蹤與速度估算

【導讀】 目標檢測與追蹤技術是計算機視覺領域最熱門的應用之一&#xff0c;廣泛應用于自動駕駛、交通監控、安全防護等場景。今天我們將帶你一步步實現一個完整的項目&#xff0c;使用YOLOv8 DeepSORT實現目標檢測、追蹤與速度估算。>>更多資訊可加入CV技術群獲取了解…

Python實例題:基于 Python 的簡單聊天機器人

Python實例題 題目 基于 Python 的簡單聊天機器人 要求&#xff1a; 使用 Python 構建一個聊天機器人&#xff0c;支持以下功能&#xff1a; 基于規則的簡單問答系統關鍵詞匹配和意圖識別上下文記憶功能支持多輪對話可擴展的知識庫 使用tkinter構建圖形用戶界面。實現至少 …

相機:Camera原理講解(使用OpenGL+QT開發三維CAD)

相機為三維場景提供了靈活便捷的視角變換和交互能力&#xff0c;通過相機操作可以實現全方位、各角度的場景瀏覽。 怎樣在三維場景中引入相機&#xff0c;怎樣處理和實現視角的放縮、移動、旋轉&#xff1f;在視角旋轉時以指定目標為中心又該怎樣處理&#xff1f; 原文&#…

開源的虛擬電廠預測數據:資源、應用與挑戰

引言 虛擬電廠(Virtual Power Plant, VPP)是一種通過聚合分布式能源資源(如太陽能、風能、儲能系統、電動汽車和可控負荷)來優化電力系統運行的數字化能源管理平臺。準確的預測數據是虛擬電廠高效運行的關鍵,而開源數據為研究者和企業提供了低成本、高透明度的解決方案。…

IDE全家桶專用快捷鍵----------個人獨家分享!!

給大家分享一下我個人整理的快捷鍵&#xff0c;其中包含對電腦的操作&#xff0c;以及在編寫代碼時的操作&#x1f680;Window系列1 WindowsR 開啟運行對話框--->輸入cmd啟動黑窗口?2 WindowsE 快速打開我的電腦 ?3 WindowsL 電腦鎖屏 ?4 WindowsD 顯示/恢復桌面 ?5 Win…

人工智能概念:RNN中的基礎Encoder-Decoder框架

文章目錄一、序列&#xff08;Seq2Seq&#xff09;轉換的核心架構二、Encoder-Decoder框架基礎原理2.1 整體工作流程2.2 編碼器&#xff08;Encoder&#xff09;詳解2.3 解碼器&#xff08;Decoder&#xff09;工作機制與缺陷三、基礎框架的核心缺陷分析&#xff08;以"歡…

R 列表:深入解析與高效應用

R 列表&#xff1a;深入解析與高效應用 引言 在R語言中&#xff0c;列表&#xff08;List&#xff09;是一種非常重要的數據結構&#xff0c;它允許我們將不同類型的數據組合在一起。列表在數據分析和統計建模中扮演著至關重要的角色。本文將深入探討R列表的概念、創建方法、…

uniapp 國密sm2加密

1. uniapp 國密sm2加密 在uniapp中使用國密SM2算法進行加密解密&#xff0c;你可以通過安裝第三方庫miniprogram-sm-crypto來實現。這個庫提供了SM2、SM3和SM4算法的實現&#xff0c;可以在小程序和uniapp項目中使用。 1.1. 安裝miniprogram-sm-crypto 首先&#xff0c;你需要…

07_持續集成與部署:DevOps的核心引擎

07_持續集成與部署:DevOps的核心引擎 引言 在快速迭代的軟件開發時代,持續集成(CI)與持續部署(CD)已成為企業提升競爭力的關鍵。通過自動化構建、測試和部署流程,CI/CD能夠顯著縮短交付周期,提高軟件質量,降低發布風險。本文將深入探討CI/CD的核心理念、實施路徑與最…

電腦休眠設置

Dont Sleep的意思就是“不要睡覺”&#xff0c;用在電腦里就是“阻止休眠”的意思。但這款軟件其實有“阻止休眠”和“允許休眠”兩個功能。 阻止休眠時可以選擇事件&#xff0c;是計時器、電池、CPU、網絡這幾個事件進行觸發阻止休假的功能。 允許休眠也可以根據自己的需求進行…

藍牙墨水屏上位機學習(3)

main.js中sendimg()函數學習&#xff0c;對應發送圖片按鈕函數代碼如下&#xff1a;async function sendimg() {const canvasSize document.getElementById(canvasSize).value;const ditherMode document.getElementById(ditherMode).value;const epdDriverSelect document.…