در این فصل چه یاد میگیری#
تا اینجا هر بار که کاری را دوباره لازم داشتی، کدش را دوباره نوشتی. این برای دو بار قابلِتحمل است و برای بیست بار فاجعه — چون وقتی باگی پیدا شود، باید بیست جا درستش کنی و حتماً یکی را جا میاندازی. در این فصل تابع را میسازی: یک تکه کد که اسم دارد، ورودی میگیرد، خروجی میدهد، و از هر جا قابلِ صدا زدن است. این نقطهای است که کدت از «چند خطِ کارکننده» به «چیزی که میشود نگهش داشت» تبدیل میشود.

آخر این فصل میتوانی:
- با
defیک تابع بسازی و صدایش بزنی - تفاوتِ
printوreturnرا بگویی و بدانی چرا این مهمترین تمایزِ فصل است - به تابع پارامتر بدهی، از جمله پارامترِ با مقدارِ پیشفرض
- بگویی «دامنهٔ متغیر» یعنی چه و چرا متغیرِ داخلِ تابع بیرون دیده نمیشود
- برای تابعت
docstringبنویسی
قبل از شروع#
از فصل ۵: دونقطه و تورفتگی یک بلوک میسازند.
از فصل ۶: حلقه و الگوی انباشتگر؛ در این فصل همانها را داخلِ تابع میگذاریم.
📓 نوتبوک: نوتبوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و بهترتیب داخلش هست.
۱. اولین تابع#
def greet():
print("سلام!")
greet()
greet()
سلام!
سلام!
def یعنی «تعریف کن». بعدش اسمِ تابع میآید، بعد پرانتز، بعد دونقطه، و بعد بدنهٔ تورفته — دقیقاً همان الگوی if و for.
نکتهٔ کلیدی: نوشتنِ def تابع را اجرا نمیکند، فقط تعریفش میکند. اجرا وقتی اتفاق میافتد که اسمش را با پرانتز صدا بزنی: greet(). بدونِ پرانتز، فقط داری به خودِ تابع اشاره میکنی، نه اجرایش.
۲. پارامتر: تابعی که ورودی میگیرد#
def greet(name):
print(f"سلام {name}!")
greet("سارا")
greet("رضا")
سلام سارا!
سلام رضا!
name داخلِ پرانتزِ تعریف یک پارامتر است — یک جای خالی که موقعِ صدا زدن پُر میشود. مقداری که موقعِ صدا زدن میدهی ("سارا") آرگومان است. این دو کلمه را زیاد جابهجا بهکار میبرند و مهم هم نیست، ولی تفاوتشان همین است: پارامتر جای خالی است، آرگومان چیزی که داخلش میگذاری.
پارامتر میتواند مقدارِ پیشفرض داشته باشد:
def greet(name, greeting="سلام"):
print(f"{greeting} {name}!")
greet("سارا")
greet("رضا", "درود")
سلام سارا!
درود رضا!
پارامترهای پیشفرض همیشه بعد از پارامترهای بدونِ پیشفرض میآیند. برعکسش SyntaxError میدهد.
۳. return: مهمترین تمایزِ این فصل#
تا اینجا توابعِ ما چاپ میکردند. ولی چاپکردن یعنی «این را به آدم نشان بده»؛ اگر بخواهی نتیجه را در محاسبهٔ بعدی بهکار ببری، به چیزِ دیگری نیاز داری:
def add_tax(price):
return price * 1.09
final = add_tax(1000000)
print(final)
print(add_tax(500000) + add_tax(300000))
1090000.0
872000.0
return مقدار را برمیگرداند به جایی که تابع صدا زده شده، و همانجا تابع تمام میشود. حالا میتوانی نتیجه را در متغیر بگذاری، جمعش کنی، یا به تابعِ دیگری بدهی.
تفاوت را با یک مقایسهٔ مستقیم ببین:
def show_double(n):
print(n * 2)
def make_double(n):
return n * 2
result_a = show_double(5)
result_b = make_double(5)
print(result_a)
print(result_b)
10
None
10
هر دو تابع «۱۰» تولید کردند، ولی فقط دومی آن را پس داد. تابعِ اول عدد را روی صفحه انداخت و دستِ خالی برگشت — برای همین result_a مقدارش None است (همان «هیچچیز»ِ فصلِ ۴).
قبل از ادامه، جواب بده: کدامیک را باید بنویسی؟ جواب: تقریباً همیشه return. تابعی که return دارد قابلِ ترکیب است — میشود خروجیاش را به تابعِ بعدی داد، در حلقه جمعش کرد، یا تستش کرد. تابعی که فقط چاپ میکند فقط یک کار بلد است. قاعدهٔ عملی: تابع حساب کند و برگرداند؛ چاپکردن کارِ کدی است که صدایش میزند.
⚠️ مواظب باش: این تلهٔ شمارهٔ یکِ تازهکارهاست. اگر
returnرا جا بیندازی، تابع بیسروصداNoneبرمیگرداند و چند خط بعدTypeError: unsupported operand type(s) for +: 'NoneType' and 'int'میگیری — و آن خطا در جایی رخ میدهد که هیچ ربطی به تابعِ معیوب ندارد. هر وقت این پیام را دیدی، اولین چیزی که باید چک کنی همین است.
۴. تابعِ واقعی: همان حلقهٔ فصلِ قبل، اسمدار#
products = [
{"name": "هدفون", "price": 850000, "stock": 12},
{"name": "کیبورد", "price": 1200000, "stock": 0},
{"name": "ماوس", "price": 430000, "stock": 7},
{"name": "وبکم", "price": 2100000, "stock": 3},
]
def in_stock_names(items):
"""نامِ محصولهایی را برمیگرداند که موجودیشان بیشتر از صفر است."""
found = []
for item in items:
if item["stock"] > 0:
found.append(item["name"])
return found
print(in_stock_names(products))
print(len(in_stock_names(products)))
['هدفون', 'ماوس', 'وبکم']
3
آن متنِ داخلِ سهگیومه در خطِ اولِ بدنه یک docstring است: توضیحی که میگوید تابع چه میکند. عادتش را از همین حالا بگیر — یک جمله کافی است، و باید بگوید چه چیزی برمیگرداند، نه اینکه چطور.
توجه کن تابع پارامترش را items نامیده، نه products. این عمدی است: حالا این تابع روی هر فهرستی با همان شکل کار میکند، نه فقط روی آن یک متغیرِ خاص.
def average_price(items):
"""میانگینِ قیمتِ یک فهرست محصول؛ برای فهرستِ خالی None برمیگرداند."""
if not items:
return None
total = 0
for item in items:
total = total + item["price"]
return total / len(items)
print(average_price(products))
print(average_price([]))
1145000.0
None
آن if not items: return None در خطِ اول یک الگوی حرفهای است: حالتِ خاص را همان اول رد کن. بدونِ آن، فهرستِ خالی به total / 0 میرسید و ZeroDivisionError میگرفتی. یک تابعِ خوب میداند ورودیِ بد هم وجود دارد.
✅ چک کن: هر دو خروجی را ببین. اگر بهجای
Noneخطا گرفتی، یعنی آن شرطِ اول را ننوشتهای یا تورفتگیاش غلط است.
📏 اندازه بگیر: برگرداندنِ
Noneبرای فهرستِ خالی یک تصمیم است، نه تنها گزینه. میشد صفر برگرداند. ولی «میانگینِ هیچ محصولی صفر است» یک ادعای غلط است، و صفر در محاسبهٔ بعدی بیسروصدا نتیجه را خراب میکند.Noneصریح میگوید «جوابی وجود ندارد». همان تفاوتِ «صفر» و «هیچ» که در فصلِ ۵ دیدی — و در ترمِ ۲ که با خانههای خالیِ جدول کار کنی، مرکزِ ماجرا میشود.
۵. دامنهٔ متغیر: داخلِ تابع، یک دنیای جدا#
counter = 100
def try_change():
counter = 5
print(f"داخل تابع: {counter}")
try_change()
print(f"بیرون تابع: {counter}")
داخل تابع: 5
بیرون تابع: 100
متغیری که داخلِ تابع ساخته میشود محلی است: با تمامشدنِ تابع از بین میرود و هیچ اثری روی بیرون ندارد. آن counter = 5 یک متغیرِ کاملاً جدا ساخت که تصادفاً همنام بود.
این خاصیت آزاردهنده بهنظر میرسد ولی در واقع بزرگترین مزیتِ تابع است: میتوانی تابعی بنویسی و مطمئن باشی هیچچیزِ دیگری در برنامه را خراب نمیکند. تابع فقط از راهِ پارامتر ورودی میگیرد و فقط از راهِ return خروجی میدهد. همین دو دروازه، کلِ قراردادش است.
🔧 اگر کار نکرد:
UnboundLocalError: cannot access local variable 'counter' where it is not associated with a valueیعنی داخلِ تابع هم از یک متغیرِ بیرونی خواندهای و هم به آن مقدار دادهای. پایتون از دیدنِ انتساب نتیجه میگیرد که متغیر محلی است، پس خواندنِ قبلی بیمعنا میشود. راهِ درست تقریباً هیچوقت دستکاریِ متغیرِ بیرونی نیست؛ مقدار را پارامتر بگیر و نتیجه راreturnکن.
🤖 از دستیارت بپرس: تابعِ
average_priceرا برایش بگذار و بپرس: «این تابع در چه ورودیهایی شکست میخورد؟ فقط فهرستشان کن و دلیلش را بگو، کد ننویس.» بعد هر موردی را که گفت خودت در نوتبوک امتحان کن. این بهترین استفاده از دستیار است: کمک به دیدنِ حالتهایی که فکرش را نکردهای، نه نوشتنِ کد بهجای تو.
واژههای تازهٔ این فصل#
| کلمه | تلفظ به حروف فارسی | یعنی چه |
|---|---|---|
| def | دِف | کلمهٔ کلیدیِ تعریفِ تابع |
| parameter / argument | پارامتر / آرگیومنت | جای خالیِ ورودی / مقداری که داخلش میگذاری |
| return | ریتِرن | مقدار را به صداکننده پس میدهد و تابع را تمام میکند |
| scope | اسکوپ | دامنه — اینکه یک متغیر کجا دیده میشود |
| docstring | داکاسترینگ | جملهٔ توضیحیِ اولِ بدنهٔ تابع |
تمرینها
اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.
در فصل بعد#
کدت حالا ساختار دارد. ولی هرچه کد بیشتر میشود، خطا هم بیشتر میشود — و تا اینجا هر بار که خطایی دیدی، فقط سرسری نگاهش کردی. فصلِ بعد یک فصلِ ⚠️ است و کاملاً به خواندنِ traceback اختصاص دارد: آن متنِ قرمزِ چندخطی که پایتون موقعِ خطا تولید میکند، در واقع دقیقترین راهنمایی است که میگیری. یاد میگیری از کدام سرش بخوانیاش و هر بخشش چه میگوید.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.