لماذا الدوال؟
تحسب السعر بعد الخصم والضريبة في خمسة أماكن من برنامجك، بنفس الأسطر منسوخة. ثم تتغيّر الضريبة: خمسة تعديلات، وستنسى واحدًا. الدالة تعطي مجموعة أسطر اسمًا، فتكتبها مرة وتستدعيها أينما احتجت. print وlen وinput كلها دوال كتبها غيرك؛ الآن تكتب دوالك.
def وreturn
def final_price(price, discount):
after_discount = price - price * discount
return round(after_discount * 1.16, 2) # منزلتان عشريتان
total = final_price(100, 0.2)
print(total) # 92.8
print(final_price(50, 0)) # 58.0
defتعرّف الدالة، ثم الاسم، ثم المعاملات (Parameters) بين قوسين، ثم:وكتلة مزاحة.returnتُنهي الدالة فورًا وتعيد القيمة لمن استدعاها، فتحفظها أو تطبعها أو تمرّرها لدالة أخرى.- عند الاستدعاء، القيم
100و0.2تُسمّى الوسائط (Arguments)، وتُربط بالمعاملات بالترتيب.
التعريف وحده لا ينفّذ شيئًا؛ الأسطر تعمل فقط حين تستدعي الدالة. وتعريف الدالة يجب أن يسبق استدعاءها في الملف.
print ليست return
def add_print(a, b):
print(a + b)
def add_return(a, b):
return a + b
x = add_print(2, 3) # يطبع 5
y = add_return(2, 3) # لا يطبع شيئًا
print(x, y) # None 5
دالة بلا return تعيد None تلقائيًّا. print تعرض القيمة على الشاشة ثم تختفي، أما return فتسلّمها للكود. الدالة التي تحسب يجب أن تعيد، ودع من يستدعيها يقرّر هل يطبع.
الخروج المبكّر
def grade(score):
if not 0 <= score <= 100:
return "درجة غير صالحة"
if score >= 90:
return "ممتاز"
if score >= 75:
return "جيد جدًا"
return "جيد"
for s in (105, 95, 80, 60):
print(s, grade(s))
بعد return لا يُنفَّذ شيء في الدالة، فلا حاجة لـ elif وelse هنا.
إعادة عدّة قيم
def stats(numbers):
return min(numbers), max(numbers), sum(numbers) / len(numbers)
low, high, avg = stats([4, 8, 15, 16, 23, 42])
print(low, high, round(avg, 2)) # 4 42 18.0
الفاصلة تجعلها صفًّا (tuple) واحدًا، والتفكيك يوزّعه على ثلاثة متغيّرات (درس الصفوف).
القيم الافتراضية والوسائط بالاسم
def greet(name, greeting="مرحبًا", punctuation="!"):
return f"{greeting} يا {name}{punctuation}"
print(greet("سارة")) # مرحبًا يا سارة!
print(greet("سارة", "أهلًا")) # أهلًا يا سارة!
print(greet("سارة", punctuation=".")) # بالاسم: تخطَّ ما في الوسط
print(greet(punctuation="؟", name="عمر")) # بالاسم: الترتيب لا يهم
المعاملات ذات القيم الافتراضية تأتي بعد التي بلا قيم. والوسائط بالاسم تجعل الاستدعاء يشرح نفسه: send(user, retry=True) أوضح من send(user, True).
فخّ القيمة الافتراضية القابلة للتغيير
def add_item(item, cart=[]): # ⚠️ قائمة واحدة لكل الاستدعاءات!
cart.append(item)
return cart
print(add_item("كتاب")) # ['كتاب']
print(add_item("قلم")) # ['كتاب', 'قلم'] ← من أين جاء الكتاب؟
def add_item_ok(item, cart=None):
if cart is None:
cart = [] # قائمة جديدة في كل استدعاء
cart.append(item)
return cart
print(add_item_ok("كتاب")) # ['كتاب']
print(add_item_ok("قلم")) # ['قلم']
القيمة الافتراضية تُنشأ مرة واحدة حين تُعرَّف الدالة، لا في كل استدعاء. مع رقم أو نص لا مشكلة، أما القائمة أو القاموس فيتراكمان بين الاستدعاءات. القاعدة: الافتراضي القابل للتغيير يُكتب None، ويُنشأ داخل الدالة.
وللدوال التي تقبل عددًا متغيّرًا من الوسائط (*args و**kwargs)، والدوال المختصرة lambda، تابع في درس lambda وrange والوسائط.
النطاق (Scope)
discount = 0.1 # عام: خارج كل الدوال
def apply(price):
result = price * (1 - discount) # يقرأ العام بلا مشكلة
return result
print(apply(200)) # 180.0
# print(result) ← NameError: محلي داخل الدالة فقط
المتغيّرات التي تُنشأ داخل الدالة محلية: تولد مع الاستدعاء وتختفي بعده، ولا تتعارض مع متغيّرات بنفس الاسم في مكان آخر. والدالة تستطيع قراءة المتغيّرات العامة، لكن الإسناد إليها من الداخل ينشئ متغيّرًا محليًّا جديدًا. يمكن تغيير ذلك بـ global، لكن الأوضح دائمًا: مرّر القيمة كمعامل وأعد النتيجة.
التوثيق وتلميحات الأنواع
def bmi(weight_kg: float, height_m: float) -> float:
"""احسب مؤشّر كتلة الجسم: الوزن ÷ مربّع الطول."""
return weight_kg / height_m ** 2
print(round(bmi(70, 1.75), 1)) # 22.9
print(bmi.__doc__)
- السلسلة الوثائقية (Docstring): نص بين
"""في أول سطر من الدالة. تعرضه المحرّرات حين تمرّ على اسم الدالة، وhelp(bmi)تطبعه. - تلميحات الأنواع (Type Hints):
: floatو-> floatتوثيق لما تتوقّعه الدالة وما تعيده. بايثون لا تفرضها وقت التشغيل، لكن المحرّرات وأدوات مثلmypyتستخدمها لاكتشاف الأخطاء قبل التشغيل.
جرّب بنفسك
📥 المدخلات (input) — قيمة في كل سطر
أخطاء شائعة
- نسيان الأقواس عند الاستدعاء:
greetبلا()لا تنفّذ الدالة، بل تشير إليها. printبدلreturn: النتيجةNoneفي الكود الذي استدعاها.- قائمة أو قاموس كقيمة افتراضية: يتراكمان بين الاستدعاءات. استخدم
None. - عدد وسائط خاطئ:
TypeError: final_price() missing 1 required positional argument: 'discount'. - الاعتماد على متغيّرات عامة تتغيّر: الدالة تعطي نتائج مختلفة لنفس المدخلات. مرّر ما تحتاجه كمعاملات.
- دالة تفعل كل شيء: إن احتجت "و" لتصفها (تقرأ وتحسب وتحفظ)، قسّمها.
🎯 التالي: القوائم (Lists): تخزين عدّة قيم معًا.