رفع خطاهای پایتون

خطای RecursionError در پایتون؛ maximum recursion depth exceeded

خطای RecursionError در پایتون؛ maximum recursion depth exceeded

🗓 آخرین به‌روزرسانی:

تابع بازگشتی (Recursive) تابعی است که خودش را صدا می‌زند. این روش برای مسائلی مثل فاکتوریل، پیمایش پوشه‌ها یا ساختارهای درختی بسیار کاربردی است؛ اما اگر شرط توقف (Base Case) را فراموش کنید یا عمق بازگشت خیلی زیاد شود، پایتون با خطای RecursionError: maximum recursion depth exceeded برنامه را متوقف می‌کند.

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

معنی خطای RecursionError در پایتون

پایتون برای جلوگیری از پر شدن حافظه پشته (Stack) و کرش کردن برنامه، تعداد فراخوانی‌های تودرتو را محدود می‌کند. این محدودیت به‌طور پیش‌فرض حدود ۱۰۰۰ است و با sys.getrecursionlimit() قابل مشاهده است. RecursionError یعنی زنجیره صدا زدن‌های تابع از این حد فراتر رفته است؛ تقریباً همیشه به خاطر نبودن یا اشتباه بودن شرط توقف.

علت‌های رایج این خطا

  • تابع بازگشتی شرط توقف ندارد.
  • شرط توقف وجود دارد ولی ورودی هرگز به آن نمی‌رسد (مثلاً عدد منفی در فاکتوریل).
  • ورودی بسیار بزرگ (مثلاً لیستی با ده‌ها هزار عضو) برای الگوریتم بازگشتی.
  • صدا زدن ناخواسته متد در خودش؛ مثل property یا __getattr__ که خودش را صدا می‌زند.

کد غلط و کد درست (با خروجی واقعی)

همه نمونه‌کدهای زیر با پایتون 3.13.5 اجرا شده‌اند و خروجی‌ها همان چیزی است که پایتون واقعاً نمایش می‌دهد.

مثال 1: تابع بدون شرط توقف

❌ کد غلط:

def countdown(n):
    print(n)
    countdown(n - 1)

countdown(3)

خروجی:

3
2
1
0
-1
-2
-3
  ...
    countdown(n - 1)
    ~~~~~~~~~^^^^^^^
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

✅ کد درست:

def countdown(n):
    if n < 0:      # base case
        return
    print(n)
    countdown(n - 1)

countdown(3)

خروجی:

3
2
1
0

مثال 2: جایگزینی بازگشت با حلقه

❌ کد غلط:

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)

print(factorial(5000))

خروجی:

Traceback (most recent call last):
  File "/home/user/project/main.py", line 6, in <module>
    print(factorial(5000))
          ~~~~~~~~~^^^^^^
  File "/home/user/project/main.py", line 4, in factorial
    return n * factorial(n - 1)
               ~~~~~~~~~^^^^^^^
  ...
    return n * factorial(n - 1)
               ~~~~~~~~~^^^^^^^
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

✅ کد درست:

def factorial(n):
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

print(factorial(5000).bit_length(), "bits")

خروجی:

54233 bits

راه‌حل قدم‌به‌قدم

  1. آخرین خطوط Traceback را ببینید تا بفهمید کدام تابع پشت سر هم تکرار شده است.
  2. بررسی کنید تابع شرط توقف دارد و در آن شرط، خودش را صدا نمی‌زند.
  3. مطمئن شوید هر فراخوانی، ورودی را یک قدم به شرط توقف نزدیک‌تر می‌کند.
  4. ورودی‌های نامعتبر (مثل عدد منفی) را در ابتدای تابع رد کنید.
  5. برای ورودی‌های بزرگ، الگوریتم را با حلقه for یا while بازنویسی کنید.

خواندن پیام RecursionError و عیب‌یابی

RecursionError با پیام maximum recursion depth exceeded یعنی تابعی آن‌قدر خودش را صدا زده که از سقف عمق بازگشت پایتون (به‌طور پیش‌فرض حدود هزار فراخوانی) عبور کرده است. پایتون این سقف را می‌گذارد تا به جای کرش کردن برنامه، خطای قابل مدیریت بدهد.

در traceback این خطا یک خط بارها تکرار می‌شود و پایتون پیام Previous line repeated را نشان می‌دهد. این تکرار نشان می‌دهد کدام تابع حالت پایه ندارد یا به آن نمی‌رسد. اول حالت پایه را بررسی کنید، بعد مطمئن شوید هر فراخوانی ورودی را به حالت پایه نزدیک‌تر می‌کند. اگر الگوریتم درست است ولی داده بزرگ است، نسخه حلقه‌ای بهتر از بالا بردن سقف است.

مقایسه نسخه بازگشتی و حلقه‌ای

import sys

def count_down(n):
    if n == 0:
        return 0
    return count_down(n - 1)

print("Limit:", sys.getrecursionlimit())
try:
    count_down(5000)
except RecursionError as error:
    print(type(error).__name__, "-", error)

def count_down_loop(n):
    while n > 0:
        n -= 1
    return n

print("Loop result:", count_down_loop(5000))

خروجی (اجراشده با Python 3.13):

Limit: 1000
RecursionError - maximum recursion depth exceeded
Loop result: 0

قدم‌به‌قدم رفع RecursionError

traceback این خطا خیلی طولانی است؛ دنبال خط تکرارشونده بگردید.

  1. تابع تکرارشونده را پیدا کنید: عبارت Previous line repeated نام تابعی را نشان می‌دهد که بی‌پایان صدا زده می‌شود.
  2. حالت پایه را بررسی کنید: آیا شرط توقف وجود دارد و برای همه ورودی‌ها (منفی، صفر، خالی) قابل دسترسی است؟
  3. پیشروی را بررسی کنید: هر فراخوانی باید ورودی را کوچک‌تر کند؛ n – 1 به جای n یا برش لیست را چک کنید.
  4. property و __getattr__ را ببینید: اگر تابع بازگشتی ندارید، یک setter که self.x را داخل خودش تنظیم می‌کند مقصر است.
  5. عمق واقعی را بسنجید: اگر منطق درست است ولی داده عمیق است، الگوریتم را با حلقه و پشته بازنویسی کنید.

یک نکته مهم دیگر: در توابعی مثل پیمایش پوشه‌ها یا درخت JSON، عمق داده را از قبل نمی‌دانید. در این موارد از همان ابتدا نسخه حلقه‌ای با یک لیست به‌عنوان پشته بنویسید تا برنامه با داده‌های بزرگ هم بدون RecursionError کار کند.

چک‌لیست پیشگیری

  • همیشه اول حالت پایه را بنویسید و آن را با کوچک‌ترین ورودی تست کنید.
  • برای داده‌های عمیق مثل درخت‌های بزرگ، از پشته دستی و حلقه while استفاده کنید.
  • برای توابع تکراری مثل فیبوناچی از functools.lru_cache استفاده کنید تا فراخوانی‌های تکراری حذف شوند.
  • مراقب property ها و __getattr__ باشید؛ دسترسی به همان ویژگی داخل خودش بازگشت بی‌پایان می‌سازد.
  • sys.setrecursionlimit را فقط با احتیاط و مقدار معقول افزایش دهید.

📌 همچنین بخوانید: خطای unexpected EOF while parsing در پایتون، خطای ‘int’ object is not subscriptable در پایتون و خطای ImportError

سؤالات متداول

آیا بالا بردن recursion limit راه‌حل خوبی است؟

با sys.setrecursionlimit() می‌توان حد را بالا برد، اما اگر شرط توقف ایراد داشته باشد مشکل حل نمی‌شود و حد خیلی بالا ممکن است باعث کرش کامل برنامه شود. بازنویسی با حلقه معمولاً بهتر است.

حد پیش‌فرض بازگشت در پایتون چقدر است؟

معمولاً ۱۰۰۰ است؛ مقدار دقیق را با import sys; print(sys.getrecursionlimit()) ببینید.

چه زمانی بازگشت انتخاب خوبی است؟

برای ساختارهای درختی و مسائلی که عمق محدودی دارند (مثل پیمایش پوشه‌ها یا تقسیم و حل) خواناترین راه است.

آیا بالا بردن recursionlimit امن است؟

تا حدی؛ مقدارهای خیلی بزرگ ممکن است باعث سرریز پشته سیستم‌عامل و کرش کامل پایتون شوند. تبدیل به حلقه راه‌حل مطمئن‌تری است.

چرا پایتون tail recursion را بهینه نمی‌کند؟

طراحان پایتون عمداً این کار را نکرده‌اند تا traceback ها کامل و خوانا بمانند؛ پس بازگشت عمیق در پایتون پرهزینه است.

RecursionError در کلاس‌ها چطور رخ می‌دهد؟

رایج‌ترین حالت، setter یا property است که داخل خودش همان ویژگی را مقداردهی می‌کند؛ از نام داخلی مثل self._value استفاده کنید.

آموزش‌های مرتبط

آموزش‌های تازه، کد آماده و رفع خطاهر هفته در کانال Dev Source؛ رایگان.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *