معاملات الدوال

من موسوعة حسوب

يمكن تعريف الدوال مع عدد غير محدّد من المعاملات، وهناك ثلاث صيغ للمعاملات يمكن استخدامها معًا في نفس الوقت.

المعاملات ذات القيم الافتراضية

يمكن تحديد قيمة افتراضية للمعامل عند تعريفه، وبهذا يمكن استدعاء الدالة مع عدد أقلّ من المعاملات المعرّفة، فعلى سبيل المثال:

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('Do you really want to quit?')‎
  • يمكن تقديم وضبط قيمة أحد المعاملات الاختيارية، مثال: ask_ok('OK to overwrite the file?', 2)
  • يمكن تقديم وضبط قيمة جميع المعاملات، مثال: ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

تختبر الكلمة المفتاحية in ما إذا كان تسلسل معيّن يحتوي على قيمة معيّنة.

تُعالج القيم الافتراضية عند تعريف الدالة وضمن النطاق الذي عُرّفت فيه الدالة، فمثلًا، تكون مخرجات الشيفرة التالية هي 5:

i = 5

def f(arg=i):
    print(arg)

i = 6
f()

تحذير مهم: تُعالج القيمة الافتراضية مرة واحدة فقط، ولهذا الأمر تأثير كبير في حال كانت القيمة الافتراضية كائنًا قابلًا للتعديل مثل القائمة والقاموس أو نسخ معظم الأصناف. فعلى سبيل المثال، تجمع الدالة التالية المعاملات الممرّرة إليها عند كل استدعاء:

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

معاملات الكلمات المفتاحية

يمكن استدعاء الدوال أيضًا باستخدام معاملات الكلمات المفتاحية والتي تأخذ الصيغة 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 و type). يمكن استدعاء هذه الدالة بأي طريقة من الطرائق التالية:

parrot(1000) # معامل موضعي واحد
parrot(voltage=1000) # معامل مفتاحي واحد
parrot(voltage=1000000, action='VOOOOOM') # معاملان مفتاحيان
parrot(action='VOOOOOM', voltage=1000000) # معاملان مفتاحيان
parrot('a million', 'bereft of life', 'jump') # ثلاثة معاملات موضعية
parrot('a thousand', state='pushing up the daisies') # معامل موقعي ومعامل مفتاحي

ولكن جميع الاستدعاءات التالية ستفشل:

parrot() # المعامل الإلزامي مفقود
parrot(voltage=5.0, 'dead') # معامل غير مفتاحي بعد معامل مفتاحي
parrot(110, voltage=220) # قيمة مكررة لنفس المعامل
parrot(actor='John Cleese') # معامل مفتاحي غير معرّف

يجب أن تأتي المعاملات المفتاحية بعد المعاملات الموقعية عند استدعاء الدالة. يجب أن تتطابق جميع المعاملات الممرّرة عند استدعاء الدالة مع المعاملات التي تأخذها الدالة (مثلًا: المعامل 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'

في حال وجود معامل شكلي (formal parameter) بالصيغة ‎**name فإنّه يستقبل قاموسًا يتضمّن جميع المعاملات المفتاحية باستثناء تلك التي تكون مرتبطة بمعامل شكلي. يمكن استخدام هذا المعامل مع معامل شكلي آخر بالصيغة ‎*name والذي يستقبل صفوفًا tuple تتضمن المعاملات الموقعية غير الموجودة في قائمة المعاملات الشكلية. (يجب أن يرد ‎*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

لاحظ أنّ الترتيب الذي طُبعت فيه المعاملات المفتاحية مطابق لترتيب المعاملات عند تقديمها في استدعاء الدالة.

قوائم المعاملات غير المحدّدة

النوع الأخير من أنواع معاملات الدوال هو المعاملات غير المحدّدة، وهي الأقل استخدامًا، وتستخدم لاستدعاء الدالة مع عدد غير محدّد من المعاملات، والتي تُجمع في صفوف. يمكن استخدام أي عدد من المعاملات الاعتيادية قبل المعاملات غير المحدّدة.

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'

استخراج قوائم المعاملات

في بعض الأحيان تكون المعاملات ضمن قائمة أو مصفوفة ولكن يجب استخراجها لاستخدامها في استدعاء دالة يتطلّب معاملات موقعية منفصلة. فعلى سبيل المثال تتوقع الدالة الداخلية range()‎ معاملي start و stop منفصلين، وفي حالة عدم كون المعاملين منفصلين عن بعضهما، يمكن كتابة استدعاء الدالة مع استخدام العامل * لاستخراج المعاملات من من القائمة أو المصفوفة:

>>> 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 !

مصادر