تابع هم یک شیء است
در پایتون تابع «شهروند درجه یک» است: میتوانید آن را در متغیر بگذارید، به تابع دیگر بفرستید، از تابع برگردانید و در لیست یا دیکشنری نگه دارید. همین ویژگی است که sorted(..., key=len) را ممکن میکند؛ ما خود تابع len را (بدون پرانتز) میفرستیم تا sorted هر وقت لازم داشت صدایش بزند.
def to_toman(rial: int) -> int:
return rial // 10
convert = to_toman # بدون پرانتز: خود تابع، نه نتیجهاش
print(convert(125_000_000)) # 12500000
def apply_all(fn, values):
return [fn(v) for v in values]
print(apply_all(to_toman, [10_000, 250_000])) # [1000, 25000]
دیکشنری از توابع: جایگزین if های طولانی
def report_daily(): return "گزارش روزانه"
def report_monthly(): return "گزارش ماهانه"
REPORTS = {"daily": report_daily, "monthly": report_monthly}
choice = "monthly"
action = REPORTS.get(choice)
print(action() if action else "گزارش ناشناخته")
lambda
lambda تابعی بینام با یک عبارت است: lambda a, b: a + b. جای درستش آرگومان کوتاه برای توابعی مثل sorted، max و min است. اگر lambda را در متغیر ذخیره میکنید (f = lambda x: ...)، همانجا با def بنویسیدش؛ نام دارد، docstring میگیرد و در پیام خطا شناخته میشود.
بازگشت (recursion)
تابع بازگشتی خودش را صدا میزند. هر تابع بازگشتی دو جزء دارد: حالت پایه که بدون بازگشت جواب میدهد، و گام بازگشتی که مسئله را کوچکتر میکند. برای ساختارهای درختی (پوشهها، دستهبندیهای تودرتوی محصول) طبیعیترین راهحل است:
catalog = {
"فرش دستباف": {"کاشان": 42, "تبریز": 17},
"فرش ماشینی": {"۷۰۰ شانه": {"کاشان": 120, "آران": 35}, "۱۲۰۰ شانه": 64},
}
def count_items(tree) -> int:
total = 0
for value in tree.values():
if isinstance(value, dict):
total += count_items(value) # گام بازگشتی
else:
total += value # حالت پایه
return total
print(count_items(catalog)) # 278
محدودیت بازگشت در پایتون
پایتون عمق بازگشت را بهطور پیشفرض به حدود ۱۰۰۰ محدود میکند (sys.getrecursionlimit()) و پس از آن RecursionError میدهد. پایتون «بهینهسازی فراخوانی انتهایی» هم ندارد. پس برای مسائلی که عمقشان به اندازهی داده است (مثل پیمایش یک لیست صدهزارتایی) از حلقه استفاده کنید و بازگشت را برای ساختارهای درختی با عمق محدود نگه دارید.
نکتههایی که کمتر کسی میداند
- دام lambda در حلقه:
[lambda: i for i in range(3)]سه تابع میسازد که همه 2 برمیگردانند، چون i را هنگام اجرا میخوانند. راهحل:lambda i=i: i. functools.lru_cacheنتیجهی توابع بازگشتی مثل فیبوناچی را کش میکند و زمان را از نمایی به خطی میرساند؛ فقط یک خط بالای تابع (@lru_cache).sys.setrecursionlimitرا بیگدار بالا نبرید؛ پشتهی واقعی سیستمعامل محدود است و برنامه ممکن است بدون پیام خطا کرش کند.- بسیاری از توابع آماده مثل
str.upperوintرا مستقیم بهعنوان key یا در map بفرستید:list(map(int, ["1", "2"]))؛ lambda اضافه لازم نیست. filter(None, items)همهی اعضای falsy (صفر، رشتهی خالی، None) را حذف میکند؛ کوتاهترین راه تمیز کردن لیست.