2  الدالة

والدالة (Function) قطعة نص برمجيّ لها اسم. وتسمى أيضًا الإجراء (Procedure). وهي من أقوى المركبات البرمجية التي تتم بها الخوارزميات.

خذ على سبيل المثال وحدة الرياضيات (math) التي يتم الاستفادة منها بعد جملة الاستيراد على النحو التالي (import):

import math

فالعالم بالرياضيات برمج هذه الدوال وأعطاها اسما، فنستطيع استعمالها في حل المسائل الرياضية:

print(math.sqrt(16))  # الجذر التربيعي
print(math.pow(2, 3)) # القوة
print(math.cos(math.radians(45))) # جيب الزاوية 45 درجة
4.0
8.0
0.7071067811865476

ولولا مفهوم تخزين الأوامر وتسميتها، للزم أن نكتب الأوامر البرمجية التي تؤدي هذه الحسابات في كل مرة نريدها. وهذا يتعذر علينا لا لأنها خطوات كثيرة فحسب بل لأنها ليست بسيطة بحيث يتقنها كل مبرمج أصلاً.

وقد تحتوي الوحدات على مسميات، كالثابت \(\pi\) الذي يستعمل في علم المثلثات:

print(math.pi)
3.141592653589793

تسمى النقطة (.) عامل إسناد (Dot Operator) في نحو العبارة math.pi أو عبارة math.sin(A)؛ وتفسرها بايثون أنها إشارة للمسمى المتضمن في الوحدة المسنَد إليها. سواءٌ كان ذلك دالة أو متغيرًا.

تذكر أن الاسم قد تشير إلى:

  1. متغير (Variable)
  2. دالة (Function)
  3. وحدة (Module)

ويجدر بالذكر أن بعض الدوال في لغة بايثون لا نحتاج فيها لإسنادها لوحدةٍ ما؛ فهي مشاعة (Global)، نحو: print()، بل ولا تحتاج إلى التصريح باستيرادها بجملة (import). ومثال ذلك أيضًا round() لتقريب العدد:

print(round(math.pi, 4))
3.1416

ويُمكن إشاعة المسميات المتضمَّنة في وِحدةٍ ما بجملة الاستيراد المبتدأة بمِن (from) بحيث لا نحتاج لإسنادها في كل مرة، وذلك يتم هذا النحو:

from math import sin, radians

c = 1000
A = 40
B = 60
C = 80

a = c * sin(radians(A)) / sin(radians(C))
h = a * sin(radians(B))
print(h)
565.2579374235679

ويُمكِن استيراد الكُلّ بعلامة النجمة (*)، على هذا النحو:

from math import *

z = cos(2*pi) - sin(pi/2)
print(z)
0.0

تنبيه: استيراد الكل (*) قد يتعارض مع مسمياتنا فيما بعد، ويصعب أن نعرف ذلك بسهولة، لذلك يجب أن يستعمل بحذر!

والمكتبة (Library) اسمٌ يطلق على مجموعة الوحدات.

وللاطلاع على الوحدات المدمجة (Built-in Modules) في لغة بايثون، يمكن الرجوع إلى صفحات بايثون المرجعية للمكتبة الأساسية (Standard Library) https://docs.python.org/3/library. حيث تجد -مثلاً-:

وهذه كلها يمكن استيرادها ثم استعمالها لأنها من ضمن بايثون نفسها.

إنشاء دالة

أسباب إنشاء الإجراء:

  • التكرار: إذا وجدت أنك تكرر نفس القطعة البرمجية مرارًا
  • التعقيد: إذا كانت العملية تحتاج لكد الذهن أو لمعرفة لا تتوفر عند الجميع
  • القابلية للتركيب: إذا كانت القطعة ككل ذات وظيفة واضحة ومحددة، ورأيت أنها تنسجم مع غيرها من القطع إذا وضعت لها اسمًا

ونمثل بتعريف الإجراء هذا (وهو مثال اعتباطي):

def calculate_bmi(weight, height):
    sq = height ** 2
    bmi = weight / sq
    return round(bmi, 2)

ويبتدأ تعريفه بكلمة def (تعني: Define)، ويليها اسمه، ويليه بين القوسين: معطياته: (weight, height). ويلي ذلك علامة الابتداء (:)، ونسرد بعدها جسده؛ وهي الأوامِر التي تعالج هذه المعطيات. وتختصُّ جُملة العودة بالناتج (return) بأنها لا تُساق إلا في جسد الإجراء. ومفادُها أنها تعود للمكان الذي استُدعيَ فيه الإجراء بالنتجية التي توضع أمام كلمة return.

ثم يحصل الاستدعاء (Call) بذكر اسم الإجراء مع عامل الاستدعاء (Call Operator) وهما القوسان بعده () وهما كالظرف تُمَرر إليه المعطيات فيهما.

result = calculate_bmi(70, 1.80)
print(result)
21.6

تعيين معطيات الإجراء بالاسم

ويجوز تعيين المعطى بالاسم لا بالموضع:

result = calculate_bmi(height=1.80, weight=70)
print(result)
21.6

ولاحظ أننا قلبنا الترتيب لنبين أنه ليس بلازمٍ إذا تمَّ التعيين بالاسم.

المعرفات في الإجراء لا تتسرب إلى الخارج

ومن خصائص الإجراء أن أي اسم يتم تعريفه داخل الإجراء فإنه معروفٌ في نطاقه وليس يتسرب العلم به إلى الخارج.

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

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

NameError: name 'bmi' is not defined

تقول رسالة الخطأ (السطر الأخير) أن المتغير bmi غير معرَّف. وهذا منطقي لأن النطاق الخارجي لا يعلم ما تكنه النطاقات الداخلية الخاصة بالإجراءات. وهو أمر مطلوب جدًّا ومرغوب في البرمجة. وذلك يعني أننا لن نتعب كثيرًا في اختيار الأسماء داخل كل إجراء، مخافة التعارض.