Skip to Content

Welcome!

This community is for professionals and enthusiasts of our products and services.
Share and discuss the best content and new marketing ideas, build your professional profile and become a better marketer together.

شما نیاز به ثبت‌نام دارید تا بتوانید با جامعه تعامل داشته باشید.
این سوال علامت‌گذاری شده است
1 پاسخ
2 نماها

یکی از مشکلاتی دیگه من این هست که تابع‌ها به صورت نا محدود صدا زده می‌شه.

من می‌خوام با مکانیسم دکوریتور حلش کنم

مثلا اگر یه تابع با پارامترهای یکسان دوباره صدا زده بشه یک مقدار پیش فرض برگردانده بشه به عنوان نیجه و در نتیجه حلقه بی نهایت فراخوانی ایجاد نشه.

این رو می‌خوام برای تابع‌های خاص استفاده کنم که مطمئن هستم نباید تکراری صدا زده بشه.

البته cal stack مهم هست و باید بدونم که قبلا این تابع رو صدا زدم یا نه.

ایا کتابخانه‌های برای ان کار وجود داره؟

آواتار
رها کردن
مولف پاسخ برتر

برای کاری که می‌خواهی انجام بدهی (تشخیص فراخوانی تکراری یک تابع با همان پارامترها در همان 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 را با طراحی صحیح حل می‌کنند، نه با دکوریتور

اما راه‌حلی که بالا نوشتم دقیقاً همان کاری است که لازم داری.


آواتار
رها کردن