وسائط الدوال — الأسماء والقيم الافتراضية والنجوم
تمرير الوسائط بالاسم، القيم الافتراضية وقواعد ترتيبها، *args و **kwargs، وفك حزم القوائم أو القواميس عند استدعاء الدوال.
- 1المشكلة
- 2الفهم
- 3أمثلة محلولة
- 4التوقع
- 5التطبيق
- 6التحدي
المشكلة التي نقوم بحلها
كانت الدوال في الدرس السابق تُستدعى بطريقة واحدة فقط: تمرير عدد من الوسائط مساوٍ تماماً لعدد المعاملات، وبنفس الترتيب الدقيق.
لكن في الواقع العملي، هذا لا يكفي أبداً. تأمل دالة مخصصة لطلبات الشراء:
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 3السطر الثاني لم يطلق أي خطأ — بل أعطى ببساطة نتيجة لا معنى لها! فعندما يكون المعاملان من نفس نوع البيانات (أو متوافقين)، لا تملك بايثون أي وسيلة لمعرفة أن الترتيب قد عُكس عن طريق الخطأ.
وماذا لو كانت معظم الطلبات تقريباً تخص عنصراً واحداً فقط؟ ستضطر لكتابة الرقم 1 في كل مرة. وإذا كانت الدالة تحتوي على أربعة خيارات إضافية اختيارية؟ ستضطر لتمرير الخيارات الأربعة جميعاً في كل استدعاء، بما في ذلك الخيارات التي لا تريد تغييرها أصلاً!
يتناول هذا الفصل الطرق الأخرى لتمرير الوسائط: بالاسم، وبالقيم الافتراضية، وبأعداد غير محددة ومفتوحة.
في نهاية هذا الدرس ستكون قادراً على
- تمرير الوسائط بالاسم (Keyword Arguments)، ومعرفة متى ينبغي لك فعل ذلك
- تعيين قيم افتراضية للمعاملات والالتزام بقواعد ترتيبها
- استقبال أي عدد من الوسائط الموضعية باستخدام
*args - استقبال وسائط بأي اسم وبأي عدد باستخدام
**kwargs - فك حزم القوائم أو القواميس وتمريرها في استدعاء الدوال
المتطلبات السابقة: الدوال — قرار واحد باسم واحد.
التمرير بالاسم (Keyword Arguments)
بدلاً من إجهاد نفسك في تذكر ترتيب المعاملات، اكتب اسم المعامل صراحة:
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x penمع استخدام الأسماء، يفقد الترتيب أهميته — فقد كُتب quantity أولاً ومع ذلك استقر في مكانه الصحيح تماماً.
متى ينبغي لك استخدام التمرير بالاسم؟ عندما لا يشرح الوسيط نفسه بنفسه. فكتابة order("pen", 3) واضحة ومفهومة. لكن كتابة send(True, False) لا تخبرك بأي شيء عن معنى هاتين القيمتين، في حين أن send(retry=True, verbose=False) توضح المعنى جلياً. كقاعدة عامة: اذكر الأسماء دائماً عند تمرير قيم منطقية True/False أو أرقام مجردة غير واضحة الدلالة.
القيم الافتراضية (Default Values)
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))1 x pen
5 x pen
5 x penتخبر العبارة quantity=1 بايثون: "افترضي أن الكمية تساوي واحداً إذا لم يحدد المتصل خلاف ذلك". وهكذا تصبح الحالة الشائعة والأكثر تكراراً قصيرة في الكتابة، مع بقاء القدرة على تخصيص الحالات النادرة متاحة دائماً.
هناك قاعدة ذهبية صارمة يجب الالتزام بها: المعاملات ذات القيم الافتراضية تأتي دائماً في النهاية.
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a defaultوالسبب منطقي وبديهي بمجرد التفكير فيه: فلو كتبنا order("pen")، كيف ستعرف بايثون لأي معامل كُتبت القيمة "pen"؟ بوضع المعاملات الإجبارية أولاً، يزول أي لبس تماماً.
أي عدد من الوسائط — المعامل *args
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0تخبر النجمة في *prices بايثون: "اجمعي كل الوسائط الموضعية المتبقية هنا معاً". فلا حاجة لتحديد عدد العناصر مسبقاً، وتمرير صفر من العناصر أمر مقبول تماماً.
وما هو نوع الكائن الذي يجمع هذه الوسائط؟
def show(*things):
print(type(things))
print(things)
show(1, "two")<class 'tuple'>
(1, 'two')إنه صف (tuple) — نفس الصف الذي درسناه في الدرس الرابع عشر! والمنطق هنا في غاية البراعة: فعدد الوسائط الممررة يتحدد ويستقر في لحظة الاستدعاء ولا ينبغي تغييره بعد ذلك، لذا فإن الوعاء غير القابل للتعديل (immutable) هو الأنسب تماماً لهذه المهمة.
الاسمpricesليس إلزامياً. الاسم الشائع اصطلاحياً في مجتمع بايثون هو*args، وهو ما ستراه في معظم الأكواد والمكتبات. لكن استخدام اسم ذي دلالة واضحة أفضل متى ما وُجد — فكتابة*pricesتخبر قارئ الكود بنوع ما يصل، بينما*argsلا تفعل ذلك.
وسائط بأي اسم — المعامل **kwargs
def describe(**fields):
print(type(fields))
for key, value in fields.items():
print(f"{key}: {value}")
describe(item="pen", quantity=3)<class 'dict'>
item: pen
quantity: 3تجمع النجمتان جميع الوسائط التي تم تمريرها بالاسم، وتكون النتيجة قاموساً (dict) — أزواجاً من الأسماء والقيم، وهو بالضبط الشكل المنطقي المتوقع.
نجمة واحدة تعطيك صَفاً (tuple)، ونجمتان تعطيك قاموساً (dictionary). احفظ هذا الاقتران وسيتضح لك كل ما تبقى بسهولة.
الترتيب الصارم للمعاملات
عند كتابة جميع الأنواع معاً في تعريف دالة واحدة، يكون الترتيب الإلزامي هو: *المعاملات العادية أولاً، ثم `args، ثم المعاملات ذات القيم الافتراضية، ثم kwargs` في النهاية.
def f(a, *args, b=1, **kwargs):قد يبدو هذا الترتيب متشدداً للوهلة الأولى، لكن سببه بسيط: يجب أن تعرف بايثون بوضوح أين يستقر كل وسيط، وبما أن *args تلتهم "كل ما تبقى من الوسائط الموضعية"، فلا يمكن لأي معامل موضعي عادي أن يأتي بعدها.
فك الحزم أثناء الاستدعاء
يمكن استخدام النجوم عند استدعاء الدالة أيضاً، حيث تؤدي حينها المهمة المعاكسة تماماً:
def order(item, quantity):
return f"{quantity} x {item}"
values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}
print(order(*values))
print(order(**fields))3 x pen
2 x bagتقوم *values بفك حزمة القائمة — وهو تماماً مثل كتابة order(values[0], values[1]). وتقوم **fields بفك حزمة القاموس، ويجب أن تتطابق مفاتيحه مع أسماء معاملات الدالة بدقة.
النجمة في تعريف الدالة تعني التجميع (gather)؛ والنجمة في استدعاء الدالة تعني فك الحزم (unpack) — نفس الرمز يؤدي وظيفتين متعاكستين حسب موقعه.
مثال متكامل
invoice.py:
# Arguments, in the order Python expects them
def invoice(customer, *items, currency="unit", discount=0.0, **extra):
total = sum(price for _, price in items)
after = total * (1 - discount)
lines = [f"Invoice for {customer}"]
for name, price in items:
lines.append(f" {name:<10} {price:>8.2f} {currency}")
lines.append(f" {'subtotal':<10} {total:>8.2f} {currency}")
if discount:
lines.append(f" {'discount':<10} {discount * 100:>7.0f}%")
lines.append(f" {'total':<10} {after:>8.2f} {currency}")
for key, value in extra.items():
lines.append(f" note {key}: {value}")
return "\n".join(lines)
print(invoice("rafi", ("pen", 15.0), ("bag", 850.0)))
print()
print(invoice("dia", ("ink", 120.0), discount=0.1, reference="A-77"))Invoice for rafi
pen 15.00 unit
bag 850.00 unit
subtotal 865.00 unit
total 865.00 unit
Invoice for dia
ink 120.00 unit
subtotal 120.00 unit
discount 10%
total 108.00 unit
note reference: A-77أربع نقاط تستحق الانتباه:
المعامل customer إجباري وكل ما عداه اختياري. لا معنى لفاتورة بدون اسم عميل، لذا فهو معامل عادي. وقد لا تحتوي الفاتورة على بنود بعد، لذا استُخدم *items.
القيمة discount=0.0 ليست مجرد قيمة افتراضية، بل تُبسط الشرط أيضاً. السطر if discount: يستفيد من قاعدة الدرس الثامن — فالقيمة 0.0 تُعامل كقيمة خاطئة (False)، وبالتالي لن يُطبع سطر الخصم إذا لم يكن هناك خصم.
المعامل `extra يستوعب ما لا تعرفه الدالة مسبقاً.** الوسيط reference="A-77" لا يطابق أي معامل رسمي، لذا استقر داخل extra` وطُبع كملاحظة ملحقة. وهذه هي الطريقة التي تتيح للدوال استقبال حقول ومعلومات إضافية لم يفكر فيها مصمم الدالة مسبقاً.
العبارة for _, price in items هي فك الحزم من الدرس الرابع عشر، حيث تشير الشرطة السفلية _ إلى أن اسم العنصر الأول غير مطلوب هنا. المتغير items هو صف من الصفوف، ويتم فك حزمة كل صف داخلي بسلاسة.
حالات الخطأ الشائعة
SyntaxError: parameter without a default follows parameter with a default وضع معامل ذي قيمة افتراضية قبل معامل إجباري. ضع دائماً المعاملات الإجبارية أولاً، وتليها المعاملات ذات القيم الافتراضية.
TypeError: order() takes 2 positional arguments but 3 were given تمرير وسائط أكثر مما تستقبله الدالة. توضح الرسالة عدد الوسائط المتوقعة والعدد الفعلي الممرر.
TypeError: order() got an unexpected keyword argument 'qty' تم تمرير وسيط بالاسم، لكن لا يوجد معامل بهذا الاسم في الدالة. تأكد من صحة كتابة الحروف — أو لاحظ أن الدالة لو كانت تحتوي على **kwargs لما اعترضت، بل كانت ستبتلعه بهدوء.
TypeError: order() got multiple values for argument 'item' تمرير نفس المعامل مرتين: مرة كموقع ومرة بالاسم — مثل order("pen", item="bag").
الدالة تعمل لكن النتيجة مقلوبة أو خاطئة عُكست الوسائط أثناء التمرير، ولم تتمكن بايثون من اكتشاف الخطأ لتطابق أنواع البيانات. مرر الوسائط بالاسم لتفادي هذا الخطأ الصامت الذي بدأنا به هذا الفصل.
*فك الحزم باستخدام ` مع عدم تطابق عدد العناصر** عند كتابة order(*values)، يجب أن تحتوي values على عدد من العناصر مساوٍ تماماً لعدد معاملات الدالة. تحقق من ذلك بواسطة print(len(values))`.
Step 4 of 6 — Predict
Check your understanding
quantity has a default. What do the two lines print?
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))- A1 x pen 5 x pen
- B1 x pen 1 x pen
- Cpen x 1 pen x 5
- DA `TypeError`
What kind of thing does *things gather?
def show(*things):
print(type(things))
print(len(things))
show(1, "two", 3.0)- A<class 'tuple'> 3
- B<class 'list'> 3
- C<class 'dict'> 3
- D<class 'tuple'> 1
The parameter with a default is placed first. What happens?
def order(quantity=1, item):
return item- AA `SyntaxError` — a parameter with a default cannot precede a required one
- BIt works, but calling `order("pen")` fails
- CIt works normally
- DA `TypeError`
Answering needs an account
Sign in to check your answers
The questions are above, and working them out in your head is the part that matters. Sign in to see the answers, the explanations and the three-level hints.
دورك الآن
اكتب ملفاً باسم message.py يحتوي على دالة واحدة بالصيغة التالية:
def notify(recipient, *lines, subject="Notice", urgent=False, **details)يجب على الدالة أن تُرجع (return) رسالة منسقة كنص بدلاً من طباعتها، تحتوي على: المستلم، والموضوع، وكل سطر من سطور الرسالة في سطر مستقل، وكل زوج من تفاصيل details كملاحظة في الأسفل. وإذا كانت قيمة urgent صحيحة (True)، فأضف تنبيهاً قبل سطر الموضوع.
ثم استدعِ الدالة بأربع طرق مختلفة:
- بتمرير المستلم فقط دون أي أسطر نصية على الإطلاق
- بتمرير ثلاثة أسطر نصية مع ترك باقي المعاملات على قيمها الافتراضية
- بتمرير
urgent=Trueوموضوع مخصصsubject، كلاهما بالاسم - بإنشاء قائمة وقاموس وفك حزمهما في الاستدعاء باستخدام
*و**
ثم تسبب عمداً في ثلاثة أخطاء واقرأ رسائلها بدقة:
- انقل المعامل ذي القيمة الافتراضية ليكون قبل
recipient - مرر المعامل باسم خاطئ إملائياً:
subjekt="hi" - اكتب الاستدعاء بصيغة:
notify("rafi", recipient="dia")
ستحصل على ثلاثة أخطاء مختلفة — إما SyntaxError أو TypeError، وجميعها أخطاء شائعة جداً في بيئات العمل الحقيقية. والتعرف على أسبابها الآن يجعلك قادراً على تشخيصها وحلها في ثوانٍ معدودة مستقبلاً.
Step 6 of 6
التحدي — the chapter quiz
عشرة أسئلة متدرجة من السهل إلى الصعب. الأسئلة الأخيرة صعبة عن قصد.
Sign in to take the quiz