4  الأفعال

الفعل: قطعة كود مخزَّنة مسمَّاة. عادة ما يكون لها عوامل متغيرة.

نحو: int(x) لإنشاء العدد الصحيح من النص. مثل: int("12") ينتج: 12.

وقد يأخذ عاملين نحو: round(x, n) مثل: round(10.259, 2) ينتج: 10.26.

وقد يأخذ عاملاً واحدًا لكنَّهُ قائمة نحو: sum(numbers) مثل: sum([1, 2, 3, 4, 5]) ينتج: 15.

وقد يكون عدد عوامله لا محدودًا نحو: print(*values). وعلامة النجمة * تشير لقبول عوامل مفكوكة على نحو ما تقدَّم، لا كالقائمة. مثل:

name = "Adam"
age = 25
print("My name is", name, "and I'm", age, "years old")
My name is Adam and I'm 25 years old

فإن print قبلت خمسة عوامل:

وقد يكون عامله غير واجب نحو: range(start, stop, step) وقد تقدم الكلام عنها في باب التكرار.

ولدينا الفعل help(func) يطلب مساعدة الفعل المعيَّن، بلا أقواس، هكذا:

help(sum)
Help on built-in function sum in module builtins:

sum(iterable, /, start=0)
    Return the sum of a 'start' value (default: 0) plus an iterable of numbers

    When the iterable is empty, return the start value.
    This function is intended specifically for use with numeric values and may
    reject non-numeric types.

وقد عرفت أن طلب التنفيذ يكون بالقوسين بعد اسمه ()، وتوضَع العوامل فيهما وهي ضربان:

وفي كل ما سبق كانت العوامل من نوع المؤثر؛ لأنها كانت تؤثر في النتيجة ولا تتأثر بها.

أما المتأثر فنحو فعل الترتيب من مكتبة القائمة: list.sort(xs) فإن المتأثر به القائمة نفسها، مثل:

xs = [40, 20, 10, 30]
list.sort(xs)

فأما جملة list.sort فإن اسم النوع list أردنا منه الفعل sort، وأتيْنا بالقوسين لطلب تنفيذه، ووضعنا المتأثر xs فيه.

ومع أننا لم نعيِّن النتيجة، فقد تغيَّرت القائمة، وهذا ما نقصده بأن المتأثر هو ما يتأثر بالفعل:

print(xs)
[10, 20, 30, 40]

بعكس الترتيب بالفعل الذي ليس من المكتبة: sorted(xs) فإنَّ القائمة فيه ليست متأثرًا؛ إذْ يُنتِجُ الفعلُ قائمةً جديدةً ولا يغير القائمة المُدخلة:

xs = [40, 20, 10, 30]
ys = sorted(xs)
print(xs, "لم يعمل الفعل في القائمة نفسها")
print(ys, "هي قائمة جديدة")
[40, 20, 10, 30] لم يعمل الفعل في القائمة نفسها
[10, 20, 30, 40] هي قائمة جديدة

ويجتمع المؤثر والمتأثر في نحو:

list.append(xs, 50)

فالفعل list.append يأخذ القائمة، ويضيف إليها القيمة 50. ولذلك نقول الأوَّل متأثر والثاني مؤثر. وإن شئت قل: متأثر ومؤثر.

print(xs)
[40, 20, 10, 30, 50]

وقد وضعت اللغات الشيئية (Object-Oriented) مثل بايثون صياغة خاصَّةً: للفِعْل المُقَدَّمِ مفعولُه. ونقصد بالمفعول الشيئ المتأثر؛ فهكذا:

وكلاهما يؤدي نفس المعنى.

وحرف النقطة . يصل لما هو مُسنَد إلى الشيء، سواءٌ كان اسم نوع نحو list أو معيَّنًا من النوع نحو xs.

ومثاله أيضًا في الفعل list.sort يأخذ القائمة، ويصنفها:

xs = [20, 10, 30, 40]
ys = [20, 10, 30, 40]

list.sort(xs)
ys.sort()
assert xs == ys

list.append(xs, 50)
ys.append(50)
assert xs == ys

وقد تُعَيَّنُ العوامل بأحد طريقتين:

  1. تعيين بالموضع: نحو: round(3.14159, 2)
  2. تعيين بالاسم: نحو: round(number=3.14159, ndigits=2) فلا يشترط فيه ترتيب العوامل.

ويجوز استعمال الطريقتين معًا في نحو: list.sort(numbers, reverse=True) ويشترط فيه تقدم التعيين بالموضع ليكون في مكانه، ثم يتبعه التعيين بالاسم حيث لا يشترط الترتيب فيه.

4.1 التعريف والتفاصيل والتنفيذ

نذلف الآن لإنشاء أفعال جديدة.

يعرَّف الفعل بـ def ويتكون من ثلاثة أجزاء:

  1. اسم الفعل: الذي يُطلَبُ به
  2. العوامل: سميت كذلك لأن نتيجة الفعل تتغير بتغيرها. ويتم تعيينها لاحقًا
  3. التفاصيل: وهي القطعة البرمجية التي جُعِلَ اسمه عنوانًا لها

أما جملة الرجوع return فإنَّها مخرج الفعل بالنتيجة لموضع الطلب.

def calculate_grade(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    elif score >= 60:
        return "D"
    else:
        return "F"

وعند طلب التنفيذ نعين العوامل ..

فينتج لنا بطلب calculate_grade(95) نسخة معيَّنة من تفاصيل الفعل، نسمّيها ظرف التنفيذ ؛ يكون فيه المتغير score=95 كأنه هكذا:

if 95 >= 90:
    return "A"
elif 95 >= 80:
    return "B"
elif 95 >= 70:
    return "C"
elif 95 >= 60:
    return "D"
else:
    return "F"

وبطلب تنفيذ calculate_grade(80) يكون الظرف هكذ:

if 80 >= 90:
    return "A"
elif 80 >= 80:
    return "B"
elif 80 >= 70:
    return "C"
elif 80 >= 60:
    return "D"
else:
    return "F"

لاحظ أن نتيجة السطرين بحسب الظرفين المختلفين:

print(calculate_grade(95))
print(calculate_grade(80))
A
B

وقد نعدد عوامل كثيرة، نحو:

def weather_condition(temperature, humidity, wind_speed):
    if temperature >= 30 and humidity >= 60 and wind_speed >= 10:
        return "Rainy"
    elif temperature >= 20 and humidity >= 50 and wind_speed >= 5:
        return "Cloudy"
    elif temperature >= 10 and humidity >= 30 and wind_speed >= 0:
        return "Sunny"
    return "Normal"

ويكون طلبها بالطريقتين كما تقدَّم، بالموضع أو بالاسم (ولا يشترط في الثانية الترتيب):

cond = weather_condition(30, wind_speed=10, humidity=60)

if cond == "Rainy":
    print("Don't forget your umbrella!")
Don't forget your umbrella!

4.2 العوامل الجائزة

الأصل في العوامل المعرَّفة الوجوب؛ إلا ما عُيِّن عند التعريف، ولو بالقيمة العدميَّة None أو ما يوازيها من القيَم الصفرية. وينسخُ ذلك التعيينَ التعيينُ عند طلب الفعل.

لو أردنا للفعل أن يتكيَّف بحسب المؤثر المعيَّن، بحيث:

  • لو عينت السلزيوس فالتحويل لفهرنهايت: convert_temperature(celsius=32)
  • لو عينت الفهرنهايت فالتحويل لسلزيوس: convert_temperature(fahrenheit=89.6)

وإليك معادلة التحويل بين نوعيْ درجة الحرارة:

\[ F = \frac{9}{5} C + 32 \]

فنعرِّفُ العوامل بقيَم عدميَّة، ونفحص وجودها بالشرط is not None لنُعمِلَها أو نهملها:

def convert_temperature(celsius = None, fahrenheit = None):
    if celsius is not None:
        fahrenheit = (9 / 5) * celsius + 32
        return fahrenheit
    elif fahrenheit is not None:
        celsius = (fahrenheit - 32) * (5 / 9)
        return celsius

نتأكد:

assert convert_temperature(celsius=32) == 89.6
assert convert_temperature(fahrenheit=89.6) == 32

4.3 التصريح بالنوع

وجاز في بايثون ذكر أنواع العوامل والنواتج، وليس بلازِم ولا مُلزِم. فوجودُها وضوحٌ للمبرمِجين ولا يُلزِمُك بها المفسِّر. نحو:

def do_something(a: int = 0, b: str = '') -> float:
    # ...

فعبارة: a: int = 0 تتكون من ثلاثة أجزاء:

  • اسم العامل: a
  • نوعه: int
  • القيمة الابتدائية: 0

وكذلك b: str = '' مثلها.

وبعد السهم نوع ناتج الفعل: -> float

ومن الأنواع المبنيَّة في بايثون:

  • int الأعداد الصحيحة، نحو: 10
  • float الأعداد العشرية، نحو: 10.5
  • str وهي نوع النص، نحو: "Salam"
  • list قائمة وإن شئت تحديد نوع العنصر الواحد فيها؛ فإنك تضعه بين القوسين المربعين، نحو:
    • list[int]
    • list[float]
    • list[str]

4.4 نطاق التسمية

ومن خصائصها أن المتغيرات في الداخل لا تظهر للخارج.

def calculate_bmi(weight: float, height: float) -> float:
    bmi = weight / (height ** 2)
    return round(bmi, 2)

فنتوقع وقوع خطأ هنا لأن bmi غير معرفة إلا في نطاق الفعل:

print(bmi)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[38], line 1
----> 1 print(bmi)

NameError: name 'bmi' is not defined

وعلى العكس فإن المعرَّفات الخارجة معروفة في الداخل؛ فيمكن أن تكون عاملة في الفعل:

max_length = 8

def check_password_strength(password: str) -> str:
    if len(password) > max_length:
        return "strong"
    elif len(password) > max_length // 2:
        return "medium"
    return "weak"

مستويات نطاق التسمية

والنصيحة العامة: أن يكون الاستعمال تاليًا لأخص نطاق، ما أمكن.

4.5 ثمرة الفعل

تُخرج الأفعال نتائج معالجتها بإحدى طريقتين:

  • الرجوع: حيث ترجِع النتيجة بجملة return لموضع طلب الفعل، وغالبًا ما يتم تعيينه نحو: y = sqrt(x)
  • العمل في مفعول:
    • مذكور، نحو: list.sort(xs) حيث تعدَّل القيمة التي يشير إليها المتغير xs
    • محذوف، نحو: print(...) حي وُضِعَت لمفعوله قيمة ابتدائية file=sys.stdout لذا تُكتَبُ النتيجة على الشاشة

والفعل دائمًا يرجع بنهاية آخر جملة فيه، لكن الذي لا يصرَّحُ فيه بكلمة الرجوع return تُقدَّر له القيمة العدمية: None ويسمى الفعل حنيها خاويًا (void).

تأمل المثال التالي الذي يأخذ عوامل ثم لا يرجع بشيء، لكنه يُظهر النتيجة على الشاشة، وقد كتبنا فيه جملة الرجوع return None لكنها مقدَّرة على أية حال، ولا يلزمنا أن نضعها:

def print_decorated(message: str, n: int) -> None:
    print("=" * n)
    print(message)
    print("=" * n)
    return None

print_decorated("Salam alykom", 15)
===============
Salam alykom
===============