سرنخ — خودآموز برنامه‌نویسی برای هوش مصنوعی (مقدماتی)

فصل ۷ از ۱۱

پیشرفت ترم
۰٪

ترم ۱ · پایتون از صفر، داخلِ Colab

تابع: به کدت اسم بده

فصل ۷پیش‌نمایش رایگان
۹ دقیقه مطالعه فصل ۷

در این فصل چه یاد می‌گیری#

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

یک ماشینِ برشی با قیفِ ورودی، یک محفظهٔ درونیِ برش‌خورده، و یک سینیِ خروجی

آخر این فصل می‌توانی:

  • با 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 اختصاص دارد: آن متنِ قرمزِ چندخطی که پایتون موقعِ خطا تولید می‌کند، در واقع دقیق‌ترین راهنمایی است که می‌گیری. یاد می‌گیری از کدام سرش بخوانی‌اش و هر بخشش چه می‌گوید.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.