در ادامه یک راهحل ساده، تمیز و قابل توسعه برای کش کردن خروجی توابع در پایتون با دکوریتور بهت میدهم. این کش:
-
بر اساس نام تابع + آرگومانها کلید میسازد
-
مدت اعتبار (duration) دارد
-
برای methodهای داخل کلاس هم کار میکند (چون self را در کلید دخالت نمیدهیم)
-
قابل توسعه برای Redis، فایل و ... است
نسخه آماده استفاده: دکوریتور cache با expiration
استفاده:
class MyClass:
@cache(duration=256)
def function_cal_1(self, x, y):
print("Real calculation running...")
return x * y
اگر دوباره همین ورودیها را صدا بزنی:
obj = MyClass()
obj.function_cal_1(2, 5)
obj.function_cal_1(2, 5)
دفعه دوم خروجی از کش برمیگردد و دیگر "Real calculation running..." چاپ نمیشود.
پیادهسازی دکوریتور cache
این همان چیزی است که نیاز داری:
import time
import functools
import hashlib
import json
_cache_storage = {} # memory cache: { key: (expire_at, value) }
def make_cache_key(func, args, kwargs):
"""تولید یک کلید یکتا بر اساس نام تابع + پارامترها"""
# args[0] = self → نباید در کش دخیل شود
pure_args = args[1:] if args and hasattr(args[0], "__class__") else args
raw_key = {
"func": f"{func.__module__}.{func.__qualname__}",
"args": pure_args,
"kwargs": kwargs,
}
raw_str = json.dumps(raw_key, sort_keys=True)
return hashlib.sha256(raw_str.encode()).hexdigest()
def cache(duration=60):
"""دکوریتور اصلی کش"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = make_cache_key(func, args, kwargs)
now = time.time()
# اگر قبلاً کش شده و هنوز معتبر است
if key in _cache_storage:
expire_at, value = _cache_storage[key]
if expire_at > now:
return value # برگرداندن از کش
# اجرای واقعی تابع
value = func(*args, **kwargs)
# ذخیره در کش
_cache_storage[key] = (now + duration, value)
return value
return wrapper
return decorator
نکات مهم
✔ کار میکند برای:
-
توابع معمولی
-
متدهای کلاسها
-
آرگومانهای معمولی (int, str, dict, list,…)
✔ برای self کش جداگانه نمیسازد
یعنی اگر دو آبجکت different باشند ولی ورودیها یکسان، کش مشترک استفاده میشود
(در ۹۹٪ موارد همین مطلوب است)
✔ پایان زمان (Expiration)
بعد از اتمام duration دوباره مقدار جدید محاسبه میشود.
⚡ اگر کش دائمی بخواهی (مثلاً روی دیسک یا redis)
خبر بده تا نسخههای زیر را برات بسازم:
-
کش روی فایل (pickle)
-
کش روی SQLite
-
کش روی Redis
-
کش async مناسب برای FastAPI / aiohttp
-
کش LRU ترکیبشده با expiration
کتابخانههای معروف برای کش کردن خروجی تابعها در پایتون
چند تا کتابخانه/ابزار آماده و محبوب وجود دارد که دقیقاً همین کار (یا بسیار شبیهش) را انجام میدهند. در ادامه مهمترینها را با توضیح میدهم 👇
| کتابخانه / ماژول |
توضیح / ویژگی |
| functools (داخل خود پایتون) |
اگر تابع خالص داری (همان ورودی = همان خروجی، بدون side-effect) — میتوانی با @functools.lru_cache(...) یا (در پایتون ≥ 3.9) با @functools.cache خروجیها را کش کنی. (Python documentation) |
| cachetools |
یک کتابخانه انعطافپذیرتر برای کش است؛ استراتژیهای مختلف (TTL, LRU, LFU و غیره) + دکوریتورهایی مثل @cached, توانایی حذف یا منقضی کردن کش پس از زمان معین و ... (GeeksforGeeks) |
| cachier |
اگر میخواهی کش «پایدار» (persistent) داشته باشی — یعنی بعد از بستن برنامه یا ریاستارت هم کش باقی بماند — cachier گزینه مناسبی است. با دکوریتور ساده کار میکند و متدهای کلاس و توابع معمولی را پوشش میدهد. (GitHub) |
⚠️ نقاط قوت و محدودیت هر کدام
-
functools.lru_cache / cache: سریع، ساده، در حافظه (RAM)، مناسب برای محاسبات سریع که ورودیها hashable هستند. اما هیچ TTL / expiration زمانی ذاتی ندارند (مگر cache_clear() دستی بزنی)، و کش همیشه در حافظه میماند. (Python documentation)
-
cachetools: انعطاف زیاد — TTL، eviction policy های مختلف، محدود کردن اندازه کش، مناسب برای مواردی که میخواهی کنترل دقیق روی کش داشته باشی. (GeeksforGeeks)
-
cachier: مناسب وقتی نتایج محاسبهشدهات را میخوای بین اجرای برنامهها نگه داری — مثلاً در اسکریپتها یا اپهایی که چند بار راهاندازی میشوند، یا زمانی که محاسبه سنگین است و میخوای کش «دائم» باشد. (GitHub)
🎯 نظر من — چه زمانی از کدام استفاده کنی
-
اگر تابع ساده است و ورودیها hashable: functools.lru_cache(maxsize=None) یا @functools.cache (پایتون ≥ 3.9) سریعترین و سادهترین انتخاب است.
-
اگر میخوای کش با expiration زمان یا eviction policy داشته باشی → cachetools مناسبتر است.
-
اگر نتیجه تابعت باید بین اجرای برنامهها (یا بین restart) حفظ شود → cachier گزینه خوبی است.
اگر بخواهی — میتوانم ۳–۴ کتابخانه معروف به همراه نمونه کد کامل برای هرکدام آماده کنم (برای متدهای کلاس، توابع ساده، با TTL، با persistence و غیره). مایل هستی اون نمونهها را هم ببینی؟