فصل ۵: توابع — از تعریف ساده تا scope و بازگشت

تعریف تابع، return، پارامتر پیش‌فرض و دام پیش‌فرض mutable

کد را یک بار بنویسید، بارها استفاده کنید

تابع یک تکه کد نام‌دار است که ورودی (پارامتر) می‌گیرد و خروجی برمی‌گرداند. هر وقت دیدید یک منطق را دو بار کپی کرده‌اید، وقت ساختن تابع است. تابع سه مزیت دارد: تکرار را حذف می‌کند، به منطق یک نام می‌دهد (که خودش مستندسازی است) و آزمودن را ممکن می‌کند.

def rug_price(width_cm, length_cm, price_per_sqm):
    area = width_cm * length_cm / 10_000
    return round(area * price_per_sqm)

p = rug_price(200, 300, 9_500_000)
print(f"{p:,} ریال")          # 57,000,000 ریال

اجزا: کلمه‌ی def، نام تابع (snake_case)، پارامترها در پرانتز، دونقطه و بدنه‌ی تورفته. return اجرای تابع را همان لحظه تمام می‌کند و مقدار را به فراخواننده برمی‌گرداند. تابعی که return ندارد (یا return خالی دارد) در واقع None برمی‌گرداند.

print در برابر return

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

return زودهنگام و چند مقدار

def shipping_cost(area, city):
    if city == "کاشان":
        return 0                      # حالت خاص را زود رد کن
    if area >= 12:
        return 0
    return 350_000 if area >= 6 else 600_000

def min_max(values):
    return min(values), max(values)   # در واقع یک tuple

low, high = min_max([12, 6, 30])

الگوی «نگهبان» (guard clause) با return زودهنگام، کد را از if های تودرتو نجات می‌دهد.

پارامتر پیش‌فرض

def price_with_tax(amount, rate=0.10):
    return round(amount * (1 + rate))

price_with_tax(1_000_000)             # با نرخ پیش‌فرض
price_with_tax(1_000_000, rate=0.09)  # با نام، خواناتر

دام معروف: پیش‌فرض تغییرپذیر

def add_item(item, basket=[]):        # اشتباه!
    basket.append(item)
    return basket

print(add_item("K1"))   # ['K1']
print(add_item("K2"))   # ['K1', 'K2']  — سبد قبلی هنوز آن‌جاست!

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

def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

نکته‌هایی که کمتر کسی می‌داند

  • همین «یک‌بار ساخته شدن» برای def log(msg, when=datetime.now()) هم صدق می‌کند: همه‌ی لاگ‌ها زمان بارگذاری ماژول را می‌گیرند. زمان را داخل تابع حساب کنید.
  • در Ruff و Pylint قاعده‌ای به نام B006 یا dangerous-default-value همین دام را خودکار پیدا می‌کند؛ روشنش کنید.
  • تابع هم یک شیء است: rug_price.__name__ نامش را می‌دهد و می‌توانید آن را در متغیر، لیست یا دیکشنری بگذارید (درس آخر همین فصل).
  • بدنه‌ی تابعی را که هنوز ننوشته‌اید با ... پر کنید؛ کد اجرا می‌شود و یادتان می‌ماند که جای خالی دارد.
  • پایتون overloading ندارد: اگر دو تابع هم‌نام تعریف کنید، دومی بی‌صدا اولی را جایگزین می‌کند. Ruff این را هم با قاعده‌ی F811 تشخیص می‌دهد.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.