4.7. المزيد عن تعريف الدوال #
من الممكن أيضًا تعريف دوال بعدد متغير من الوسائط. هناك ثلاثة أشكال، يمكن دمجها.
4.7.1. قيم الوسيطات الافتراضية #
الطريقة الأكثر فائدة هي تحديد قيمة افتراضية لوسيطة واحدة أو أكثر. يؤدي هذا إلى إنشاء دالة يمكن استدعاؤها بعدد وسيطات أقل مما هو مسموح به. على سبيل
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
يمكن استدعاء هذه الدالة بعدة طرق:
- إعطاء الوسيطة الإلزامية فقط: ask_ok(‘هل تريد حقًا الخروج؟’)
- إعطاء أحد الوسيطات الاختيارية: ask_ok(‘هل تريد استبدال الملف؟’, 2)
- أو حتى إعطاء جميع الوسيطات: ask_ok(‘هل تريد استبدال الملف؟’, 2, ‘هيا، نعم أو لا فقط!’)
هذا يُقدّم المثال أيضًا الكلمة المفتاحية in. هذا يختبر ما إذا كان التسلسل يحتوي على قيمة معينة أم لا.
تُقيّم القيم الافتراضية عند تعريف الدالة في نطاق التعريف، بحيث:
i = 5
def f(arg=i):
print(arg)
i = 6
f()
سوف يطبع 5.
تحذير هام: تُقيّم القيمة الافتراضية مرة واحدة فقط. هذا يُحدث فرقًا عندما تكون القيمة الافتراضية كائنًا قابلًا للتغيير مثل قائمة أو قاموس أو مثيلات لمعظم الفئات. على سبيل المثال، تُجمّع الدالة التالية الوسائط المُمرّرة إليها عند استدعاءات لاحقة:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
هذا سيطبع:
[1]
[1, 2]
[1, 2, 3]
إذا كنت لا ترغب في مشاركة القيمة الافتراضية بين الاستدعاءات اللاحقة، يمكنك كتابة الدالة كما يلي:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
4.7.2. الوسائط المفتاحية #
يمكن أيضًا استدعاء الدوال باستخدام الوسائط المفتاحية من صيغة kwarg=value. على سبيل المثال، الدالة التالية:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
تقبل الدالة مُعاملًا واحدًا مطلوبًا (voltage) وثلاث مُعاملات اختيارية (state, action, and type). يمكن استدعاء هذه الدالة بأي من الطرق التالية:
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
ولكن جميع الاستدعاءات التالية ستكون غير صالحة:
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
في استدعاء دالة، يجب أن تتبع وسيطات الكلمات المفتاحية الوسيطات الموضعية. يجب أن تتطابق جميع وسيطات الكلمات المفتاحية المُمررة مع أحد الوسائط المقبولة من قِبل الدالة (مثلاً، actor ليس وسيطة صحيحة لدالة parrot)، وترتيبها غير مهم. يشمل هذا أيضًا الوسائط غير الاختيارية (مثلاً، parrot(voltage=1000) صالحة أيضًا). لا يجوز لأي وسيطة أن تتلقى قيمة أكثر من مرة. إليك مثال يفشل بسبب هذا التقييد:
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for keyword argument 'a'
عند وجود مُعامل رسمي نهائي من النموذج **name، فإنه يستقبل قاموسًا (انظر “تعيين الأنواع” – dict) يحتوي على جميع مُعاملات الكلمات المفتاحية باستثناء تلك المُقابلة لمُعامل رسمي. يُمكن دمج هذا مع مُعامل رسمي من النموذج *name (المُوضح في القسم الفرعي التالي) والذي يستقبل مجموعة تحتوي على مُعاملات الموضع التي تتجاوز قائمة المُعاملات الرسمية. (يجب أن يكون *name قبل **name)ـ) على سبيل المثال، إذا عرّفنا دالة كالتالي:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
يمكن تسميتها كالتالي:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
وبالطبع ستُطبع:
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
لاحظ أن ترتيب طباعة وسيطات الكلمات المفتاحية مضمون ليطابق الترتيب الذي وردت به في استدعاء الدالة.
4.7.3. معلمات خاصة #
افتراضيًا، يمكن تمرير الوسيطات إلى دالة بايثون إما حسب الموضع أو صراحةً حسب الكلمة المفتاحية. لتحسين سهولة القراءة والأداء، من المنطقي تقييد طريقة تمرير الوسيطات بحيث لا يحتاج المطور إلا إلى النظر إلى تعريف الدالة لتحديد ما إذا كانت العناصر تُمرر حسب الموضع، أو حسب الموضع أو الكلمة المفتاحية، أو حسب الكلمة المفتاحية.
قد يبدو تعريف الدالة كما يلي:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
حيث / و * اختياريان. عند استخدام هذه الرموز، تشير إلى نوع المعلمة من خلال كيفية تمرير الوسائط إلى الدالة: موضعي فقط، موضعي أو كلمة مفتاحية، وكلمة مفتاحية فقط. تُعرف معلمات الكلمات المفتاحية أيضًا بالمعلمات المسماة.
4.7.3.1. معلمات الموضع أو الكلمة المفتاحية #
إذا لم يكن / و * موجودين في تعريف الدالة، فيمكن تمرير الوسائط إلى الدالة حسب الموضع أو الكلمة المفتاحية.
4.7.3.2. معلمات الموضع فقط #
بالنظر إلى هذا بمزيد من التفصيل، من الممكن تحديد معلمات معينة على أنها موضعية فقط. إذا كانت الدالة ذات وضعية فقط، فإن ترتيب المعاملات مهم، ولا يمكن تمريرها باستخدام الكلمة المفتاحية. توضع معاملات الوضعية فقط قبل / (شرطة مائلة للأمام). تُستخدم / لفصل معاملات الوضعية فقط منطقيًا عن بقية المعاملات. إذا لم يكن هناك / في تعريف الدالة، فلا توجد معاملات وضعية فقط.
يمكن أن تكون المعاملات التي تلي / معاملات وضعية أو كلمات مفتاحية أو معاملات كلمات مفتاحية فقط.
4.7.3.3. وسيطات الكلمات المفتاحية فقط #
لتمييز المعاملات على أنها ذات وضعية فقط، مع الإشارة إلى أنه يجب تمرير المعاملات باستخدام وسيطة الكلمة المفتاحية، ضع * في قائمة الوسيطات قبل أول معامل كلمة مفتاحية فقط مباشرةً.
4.7.3.4. أمثلة على الدوال #
انظر إلى تعريفات الدوال التالية مع التركيز على علامتي / و*:
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
تعريف الدالة الأولى، standard_arg، وهو الشكل الأكثر شيوعًا، لا يضع أي قيود على طريقة الاستدعاء، ويمكن تمرير الوسائط بواسطة الموضع أو الكلمة المفتاحية:
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
الدالة الثانية، pos_only_arg، تقتصر على استخدام الموضع فقط. المعاملات، حيث يوجد / في تعريف الدالة:
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
الدالة الثالثة kwd_only_args تسمح فقط بوسيطات الكلمات الرئيسية كما هو موضح بعلامة * في تعريف الدالة:
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
وتستخدم الدالة الأخيرة جميع اصطلاحات الاستدعاء الثلاثة في نفس الدالة. التعريف:
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'
أخيرًا، لننظر إلى تعريف هذه الدالة الذي قد يتعارض بين اسم الوسيطة الموضعية و **kwds الذي يحتوي على الاسم كمفتاح:
def foo(name, **kwds):
return 'name' in kwds
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
ولكن باستخدام / (وسيطات موضعية فقط)، يُمكن ذلك لأنه يسمح باستخدام name كوسيط موضعي و’name’ كمفتاح في وسيطات الكلمات المفتاحية:
def foo(name, /, **kwds):
return 'name' in kwds
>>> foo(1, **{'name': 2})
True
بمعنى آخر، يُمكن استخدام أسماء معلمات الموضعية فقط في **kwds دون أي غموض.
٤.٧.٣.٥. ملخص #
ستحدد حالة الاستخدام المعاملات المُستخدمة في تعريف الدالة:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
للإرشاد:
- استخدم “الموضع فقط” إذا كنت ترغب في إخفاء أسماء المعاملات عن المستخدم. يُعد هذا مفيدًا عندما لا يكون لأسماء المعاملات معنى حقيقي، أو إذا كنت ترغب في فرض ترتيب المعاملات عند استدعاء الدالة، أو إذا كنت بحاجة إلى استخدام بعض المعاملات الموضعية وكلمات مفتاحية عشوائية.
- استخدم “الموضع فقط” عندما يكون للأسماء معنى ويكون تعريف الدالة أكثر وضوحًا من خلال توضيح الأسماء، أو إذا كنت ترغب في منع المستخدمين من الاعتماد على موضع المعاملة المُمررة.
- بالنسبة لواجهة برمجة التطبيقات (API)، استخدم “الموضع فقط” لمنع تغييرات واجهة برمجة التطبيقات المُعطلة في حال تعديل اسم المعاملة في المستقبل.
4.7.4. قوائم المعاملات العشوائية #
أخيرًا، الخيار الأقل استخدامًا هو تحديد إمكانية استدعاء الدالة بعدد عشوائي من المعاملات. سيتم تجميع هذه الوسائط في مجموعة (انظر المجموعات والمتتاليات). قبل العدد المتغير للوسائط، قد لا يكون هناك أي وسيطات عادية أو أكثر.
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
عادةً، تكون هذه الوسائط المتغيرة الأخيرة في قائمة المعاملات الرسمية، لأنها تجمع جميع وسيطات الإدخال المتبقية التي تُمرر إلى الدالة. أي معاملات رسمية تظهر بعد معامل *args هي وسيطات “للكلمات المفتاحية فقط”، أي أنه لا يمكن استخدامها إلا ككلمات مفتاحية بدلاً من وسيطات موضعية.
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
4.7.5. فك حزم قوائم الوسائط
يحدث الوضع المعاكس عندما تكون الوسائط موجودة بالفعل في قائمة أو مجموعة، ولكن يلزم فك حزمها لاستدعاء دالة تتطلب وسائط موضعية منفصلة. على سبيل المثال، تتوقع دالة range() المدمجة وسيطات بداية ونهاية منفصلة. إذا لم تكن متوفرة بشكل منفصل، فاكتب استدعاء الدالة باستخدام عامل * لفك ضغط الوسائط من قائمة أو مجموعة:
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]
وبنفس الطريقة، يمكن للقواميس تقديم وسيطات الكلمات المفتاحية باستخدام عامل **:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
4.7.6. تعبيرات لامدا #
يمكن إنشاء دوال صغيرة مجهولة المصدر باستخدام الكلمة المفتاحية lambda. تُرجع هذه الدالة مجموع وسيطيها: lambda a, b: a+b. يمكن استخدام دوال لامدا أينما تكون هناك حاجة لكائنات دالة. وهي مُقيَّدة نحويًا بتعبير واحد. أما دلاليًا، فهي مجرد صقل نحوي لتعريف دالة عادية. كما هو الحال مع تعريفات الدوال المتداخلة، يمكن لدوال لامدا أن تُرجع متغيرات من النطاق الذي تحتويه:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
يستخدم المثال أعلاه تعبير لامدا لإرجاع دالة. استخدام آخر هو تمرير دالة صغيرة كمعامل:
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
4.7.7. سلاسل التوثيق #
فيما يلي بعض القواعد المتعلقة بمحتوى وتنسيق سلاسل التوثيق.
يجب أن يكون السطر الأول دائمًا ملخصًا موجزًا وموجزًا لغرض الكائن. وللإيجاز، يجب ألا يذكر اسم الكائن أو نوعه صراحةً، إذ يمكن الوصول إليهما بطرق أخرى (إلا إذا كان الاسم فعلًا يصف عملية دالة). يجب أن يبدأ هذا السطر بحرف كبير وينتهي بنقطة.
إذا كان هناك المزيد من الأسطر في سلسلة التوثيق، فيجب أن يكون السطر الثاني فارغًا، ليفصل الملخص بصريًا عن بقية الوصف. يجب أن تكون الأسطر التالية فقرة واحدة أو أكثر تصف قواعد استدعاء الكائن، وآثاره الجانبية، وما إلى ذلك.
لا يزيل محلل بايثون المسافة البادئة من حروف السلاسل النصية متعددة الأسطر في بايثون، لذا يتعين على الأدوات التي تعالج التوثيق إزالة المسافة البادئة عند الحاجة. يتم ذلك باتباع الاصطلاح التالي: يُحدد أول سطر غير فارغ بعد السطر الأول من السلسلة النصية مقدار المسافة البادئة لسلسلة التوثيق بأكملها. (لا يمكننا استخدام السطر الأول لأنه عادةً ما يكون مجاورًا لعلامتي الاقتباس الافتتاحيتين للسلسلة النصية، لذا فإن المسافة البادئة الخاصة به غير ظاهرة في النص الأصلي). تُزال المسافة البادئة “المكافئة” لهذه المسافة البادئة من بداية جميع أسطر السلسلة النصية. يجب ألا تظهر الأسطر ذات المسافة البادئة الأقل، ولكن في حال ظهورها، يجب إزالة جميع المسافات البادئة فيها. يجب اختبار تكافؤ المسافات البادئة بعد توسيع علامات التبويب (إلى 8 مسافات، عادةً). فيما يلي مثال على سلسلة توثيق متعددة الأسطر:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
4.7.8. تعليقات الدوال #
تعليقات الدوال هي معلومات وصفية اختيارية تمامًا حول الأنواع المستخدمة في الدوال المُعرّفة من قِبل المستخدم (انظر PEP 3107 وPEP 484 لمزيد من المعلومات).
تُخزّن التعليقات في سمة __annotations__ للدالة كقاموس، ولا تؤثر على أي جزء آخر منها. تُعرّف تعليقات المعاملات بنقطتين رأسيتين بعد اسم المعامل، متبوعة بتعبير يُقيّم قيمة التعليق. تُعرَّف تعليقات الإرجاع بعلامة حرفية ->، متبوعة بتعبير، بين قائمة المعاملات والنقطتين اللتين تُشيران إلى نهاية جملة def. يحتوي المثال التالي على وسيطة موضعية، ووسيطة كلمة مفتاحية، وقيمة الإرجاع المُعلَّقة:
>>> def f(ham: str, eggs: str = 'eggs') -> str:
... print("Annotations:", f.__annotations__)
... print("Arguments:", ham, eggs)
... return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'