برای کاری که میخواهی انجام بدهی (تشخیص فراخوانی تکراری یک تابع با همان پارامترها در همان call-stack و جلوگیری از loop یا recursion بینهایت)، کتابخانه آماده و استانداردی وجود ندارد؛ چون این کار خیلی خاص و وابسته به منطق برنامه است.
اما دو مکانیسم معمول برای حل این مشکل وجود دارد:
راهکار ۱ — دکوریتور “تشخیص فراخوانی تکراری در یک Call Stack”
ایده این است:
-
یک stack-local set نگه میداریم
-
هر بار تابع صدا زده شد، چک میکنیم آیا کلید (تابع + آرگومانها) در همین call-stack وجود دارد
-
اگر هست → مقدار پیشفرض برگردان
-
اگر نیست → وارد stack شو، اجرا کن، موقع خروج پاک کن
✔ نسخه آماده:
import functools
import hashlib
import json
import contextvars
# یک متغیر محلی برای هر call-stack
_call_stack_cache = contextvars.ContextVar("call_stack_cache", default=set())
def make_key(func, args, kwargs):
raw = {
"func": func.__qualname__,
"args": args,
"kwargs": kwargs,
}
return hashlib.sha256(json.dumps(raw, sort_keys=True).encode()).hexdigest()
def prevent_recursive(default_return=None):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = make_key(func, args, kwargs)
stack = _call_stack_cache.get()
if key in stack:
# فراخوانی تکراری در همان call stack → جلوگیری
return default_return
# ورود به stack
new_stack = set(stack)
new_stack.add(key)
token = _call_stack_cache.set(new_stack)
try:
return func(*args, **kwargs)
finally:
# خروج از stack
restored_stack = _call_stack_cache.get()
restored_stack.discard(key)
_call_stack_cache.reset(token)
return wrapper
return decorator
✔ استفاده:
@prevent_recursive(default_return=None)
def my_func(x):
return my_func(x) # برای تست recursion loop
با این دکوریتور، اولین فراخوانی اجرا میشود
فراخوانی دوم (در همان call stack) → default_return برمیگردد
در نتیجه loop بینهایت نمیشود.
راهکار ۲ — استفاده از functools.lru_cache (در برخی موارد)
اگر مشکل recursion نیست، بلکه توابع با پارامتر یکسان زیاد صدا زده میشوند:
@functools.lru_cache(maxsize=None)
def my_func(x, y):
...
ولی این call-stack-aware نیست و recursion loop را متوقف نمیکند.
❌ چرا کتابخانه آماده وجود ندارد؟
چون:
-
این رفتار بسیار خاص است
-
call-stack detection معمولاً سطح پایین است
-
اکثر سیستمها recursion را با طراحی صحیح حل میکنند، نه با دکوریتور
اما راهحلی که بالا نوشتم دقیقاً همان کاری است که لازم داری.