logo
همه

با توابع لدر پایتون بیشتر‌آشنا شوید!

Unes Moradi - 1403/12/14
banner image

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

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


مفهوم توابع بازگشتی چیست؟

بازگشت (Recursion) فرآیندی است که در آن یک تابع خود را به عنوان بخشی از عملیات داخلی فراخوانی می‌کند. به عبارت ساده‌تر تابع بازگشتی در بدنه خود، یک یا چند بار خودش را صدا می‌زند. این فرآیند به‌ظاهر ساده امکان حل مسائل پیچیده را با تقسیم آن‌ها به مسائل کوچک‌تر فراهم می‌کند.

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

  1. اگر عدد صفر بود، مقدار ۱ را برگردان.

  2. در غیر این صورت، عدد را در فاکتوریل عدد قبلی ضرب کن.

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


ساختار توابع بازگشتی در پایتون

ساختار تابع بازگشتی از سه بخش اصلی تشکیل شده:

  1. شرط توقف (Base Case): جایی است که در آن تابع دیگر خودش را فراخوانی نکرده و مقداری مشخص را برمی‌گرداند. بدون وجود شرط توقف تابع به طور نامحدود خودش را فراخوانی کرده و منجر به خطای بازگشت (RecursionError) می‌شود.

  2. فراخوانی خود تابع (Recursive Call): در این بخش تابع خودش را با پارامترهای جدید فراخوانی می‌کند. این پارامترها باید به‌گونه‌ای انتخاب شوند که مسئله را به سمت شرط توقف پیش ببرند.

  3. بدنه تابع (Function Body): شامل محاسبات و منطق حل مسئله است. این بخش می‌تواند قبل یا بعد از فراخوانی بازگشتی قرار گیرد.


مثال‌های عملی از توابع بازگشتی پایتون

برای درک بهتر مفهوم تابع بازگشتی در پایتون چند مثال عملی را با هم مرور می‌کنیم.


محاسبه فاکتوریل عدد

فاکتوریل هر عدد طبیعی n برابر است با حاصل‌ضرب تمام اعداد طبیعی از ۱ تا n؛ برای مثال: 

۵! = ۵ * ۴ * ۳ * ۲ * ۱ = ۱۲۰

تابع بازگشتی برای محاسبه فاکتوریل به این صورت است:

def factorial(n):

  # شرط توقف

  if n == 0:

    return 1

  # فراخوانی بازگشتی

  else:

    return n * factorial(n-1)


# مثال استفاده

print(factorial(5)) # خروجی: 120


محاسبه سری فیبوناچی

در سری فیبوناچی هر عدد برابر است با مجموع دو عدد قبلی. دو عدد اول این سری ۰ و ۱ هستند؛ برای مثال:

۰, ۱, ۱, ۲, ۳, ۵, ۸, ۱۳, ...

تابع بازگشتی برای محاسبه nامین عدد فیبوناچی:

def fibonacci(n):

  # شرط‌های توقف

  if n == 0:

    return 0

  elif n == 1:

    return 1

  # فراخوانی بازگشتی

  else:

    return fibonacci(n-1) + fibonacci(n-2)


# مثال استفاده

print(fibonacci(7)) # خروجی: 13

در این روش هر مقدار با محاسبه دو مقدار قبلی آن به‌دست می‌آید. البته این پیاده‌سازی از نظر کارایی بهینه نیست و در مسائل واقعی از روش‌های بهینه‌تر مثل برنامه‌نویسی پویا استفاده می‌شود.


حل مسئله توان عددی

فرض کنید می‌خواهیم عدد x را به توان y برسانیم. تابع بازگشتی این مسئله بدین ترتیب است:

def power(x, n):

    # شرط توقف

    if n == 0:

        return 1

    # فراخوانی بازگشتی

    else:

        return x * power(x, n-1)


# مثال استفاده

print(power(2, 3))  # (2*2*2)  خروجی: 8


مزایا و معایب توابع بازگشتی در پایتون

استفاده از توابع بازگشتی در پایتون مزایا و معایب خاص خود را دارد که باید با آنها آشنا باشید.

مزایای توابع بازگشتی پایتون:

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

  • حل مسائل پیچیده: برخی مسائل مانند جستجوی دودویی، مرتب‌سازی Merge Sort و عبور از گراف‌ها به‌صورت بازگشتی بهینه‌تر حل می‌شوند. این ویژگی برای توسعه‌ برنامه‌ها در فریم ورک جنگو بسیار کاربرد دارد.

معایب توابع بازگشتی پایتون:

  • مصرف حافظه بالا: هربار که تابع بازگشتی خودش را فراخوانی می‌کند، یک فریم جدید در پشته ایجاد می‌شود. اگر تعداد فراخوانی‌ها زیاد باشد، حافظه زیادی مصرف‌شده و ریسک سرریز پشته (Stack Overflow) وجود دارد.

  • احتمال خطای بازگشت بیش‌ازحد (Recursion Limit Exceeded): پایتون برای جلوگیری از فراخوانی‌های بی‌نهایت یک حد مجاز برای تعداد بازگشت‌ها تعیین کرده. اگر تعداد فراخوانی‌ها از این حد بیشتر شود، خطای Recursive Error رخ می‌دهد.


تفاوت توابع بازگشتی و تکراری (Iterative)

همان‌طور که گفتیم در رویکرد بازگشتی، تابع خودش را فراخوانی می‌کند تا به نتیجه برسد. از طرف دیگر در رویکرد تکراری از حلقه‌ها (مانند for و while) برای تکرار یک عملیات استفاده می‌شود. 

انتخاب میان این دو روش به نوع مسئله و کارایی مورد نظر بستگی دارد. گاهی اوقات پیاده‌سازی یک راه‌حل به‌صورت بازگشتی، بسیار کوتاه‌تر و قابل‌فهم‌تر از حالت تکراری آن است؛ به خصوص اگر ذات مسئله به صورت بازگشتی تعریف شده باشد. 

در جدول زیر تفاوت‌های اصلی این دو رویکرد را می‌بینید:


ویژگی

توابع بازگشتی (Recursive)

توابع تکراری (Iterative)

تعریف

تابعی که خود را فراخوانی می‌کند.

از حلقه‌های for یا while برای تکرار استفاده می‌کند.

شرط توقف

باید یک شرط توقف مشخص داشته باشد.

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

پیاده‌سازی

اغلب کد کوتاه‌تر و خواناتری دارد.

پیچیدگی کمتری دارد و مدیریت آن آسان‌تر است.

مصرف حافظه

به دلیل ایجاد فریم پشته مصرف حافظه بیشتری دارد.

حافظه کمتری استفاده می‌کند چون فریم‌های پشته جدید ایجاد نمی‌شوند

سرعت اجرا

اگر بهینه‌سازی نشود، کندتر است.

سریع‌تر اجرا می‌شود چون سربار فراخوانی ندارد.

مثال فاکتوریل

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

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

کاربردها

پیمایش درخت‌ها، دنباله فیبوناچی، برج‌های هانوی

محاسبات عددی، پردازش داده‌ها، حلقه‌های نامحدود


برای درک بهتر تفاوت این دو رویکرد فرض کنید می‌خواهیم مجموع اعداد ۱ تا n را محاسبه کنیم.

رویکرد تکراری:

def sum_iterative(n):

  sum = 0

  for i in range(1, n + 1):

    sum += i

  return sum


رویکرد بازگشتی:

def sum_recursive(n):

  if n == 1:

    return 1

  else:

    return n + sum_recursive(n-1)

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

از سوی دیگر در پیاده‌سازی الگوریتم‌های مرتبط با درخت‌ها و گراف‌ها، رویکرد بازگشتی به‌طور معمول طبیعی‌تر و خواناتر است. این موضوع در توسعه وب و استفاده از فریم ورک flask نیز صدق می‌کند.


بهینه‌سازی توابع بازگشتی پایتون

چالش اصلی توابع بازگشتی پایتون محاسبات تکراری است. مثلا در محاسبه سری فیبوناچی با روش بازگشتی ساده، مقادیر بارها و بارها محاسبه می‌شوند. راه‌حل این مشکل ذخیره‌سازی موقت (Memoization) است. 

در این تکنیک نتایج فراخوانی‌های قبلی تابع را ذخیره می‌کنیم تا هنگام نیاز به جای محاسبه مجدد از آن استفاده کنیم. این کار به طور چشمگیری تعداد فراخوانی و مصرف حافظه را کاهش می‌دهد.

محاسبه دنباله فیبوناچی بدون بهینه‌سازی:

def fibonacci(n):

    if n <= 1:

        return n

    return fibonacci(n - 1) + fibonacci(n - 2)

این کد برای فیبوناچی ۱۰ مقدار فیبوناچی ۹ و ۸ را محاسبه می‌کند که باعث اتلاف منابع و کاهش سرعت اجرا می‌شود.

محاسبه با بهینه‌سازی با ذخیره موقت:

from functools import lru_cache


@lru_cache(maxsize=None)

def fibonacci_memoized(n):

    if n <= 1:

        return n

    return fibonacci_memoized(n - 1) + fibonacci_memoized(n - 2)

در این مثال با استفاده از دکوراتور lru_cache نتایج محاسبات قبلی ذخیره شده و سرعت اجرای تابع به‌طور قابل‌توجهی بیشتر می‌شود.

روش دیگر برای حل مشکل حافظه آزادسازی مقادیری است که دیگر به آنها نیازی ندارید. همچنین می‌توان از روش بازگشت دم (Tail Recursion) بهره برد که در آن ابتدا محاسبات انجام شده و در پایان فراخوانی انجام می‌شود. 

مثال: بازگشت دم برای محاسبه فاکتوریل

def factorial_tail_recursive(n, acc=1):

    if n == 0:

        return acc

    return factorial_tail_recursive(n - 1, n * acc)

در این روش مقدار محاسبه شده در متغیر acc ذخیره شده و دیگر نیازی به نگهداری مقادیر قبلی در پشته نیست.


خطاهای رایج در استفاده از توابع بازگشتی پایتون

استفاده نادرست از توابع بازگشتی می‌تواند به خطاهای پیچیده‌ای منجر شود. در ادامه به دو مورد از رایج‌ترین خطاها در این زمینه می‌پردازیم.


حلقه‌های بی‌پایان

اگر تابع بازگشتی به‌درستی تعریف نشود به خاطر ماهیتش به‌طور مداوم خودش را فراخوانی می‌کند و در نهایت با خطای Recursive Error مواجه می‌شویم. این خطا نشان می‌دهد که پایتون از حداکثر عمق فراخوانی توابع (Maximum Recursion Depth) عبور کرده است.

برای مثال اگر در تابع فاکتوریل شرط توقف را حذف کنیم، به‌صورت بی‌پایان اجرا خواهد شد:

def factorial_wrong(n):

    return n * factorial_wrong(n - 1)


فراموش کردن شرط توقف

فراموش کردن شرط توقف یا تعریف نادرست آن دیگر خطای رایج در توابع بازگشتی است. در زبان برنامه نویسی پایتون شرط توقف باید طوری تعریف شود که در نهایت به آن برسیم. مثلا در تابع فیبوناچی اگر شرط پایه را فقط برای n=0 تعریف کنیم، برای مقادیر منفی n دچار مشکل خواهیم شد:

def fibonacci_incorrect(n):

    if n == 0:  # شرط پایه ناقص

        return 0

    else:

        return fibonacci_incorrect(n-1) + fibonacci_incorrect(n-2)

تابع فوق عملکرد برنامه را مختل کرده و به نتایج نادرست منجر می‌شود. 

روش صحیح، تعریف شرط پایه برای همه حالت‌های ممکن است:

def fibonacci_correct(n):

    if n < 0:

        raise ValueError("ورودی نمی‌تواند منفی باشد")

    elif n <= 1:  # شرط پایه کامل

        return n

    else:

        return fibonacci_correct(n-1) + fibonacci_correct(n-2)

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


جمع‌بندی

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

اگر دنبال مسیر آموزشی منسجم و جامع برای یادگیری پایتون هستید، بوتکمپ برنامه‌نویسی بک‌اند پایتون جنگو در «کلاسور» گزینه مناسبی برای شماست. در این بوتکمپ که با حضور منتورهای خبره برگزار می‌شود، با انجام پروژه‌های عملی به یک برنامه‌نویس پایتون حرفه‌ای تبدیل شده و در پایان دوره هم به بازار کار معرفی می‌شوید.

مطالب مشابه

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

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

دیدگاه‌ها

دیدگاهی ثبت نشده، شما اولین نفر باشدی.
یاد بگیر، تجربه کسب کن،
تو بهترین شرکت‌ها استخدام شو.
K . E . L . A . A . S . O . R
| تمامی حقوق کپی‌رایت محفوظ است. ۱۴۰۲ شرکت کلاسور |