رأينا في جميع ما سبق كيف أن بايثون لغة أمريَّة (Imperative)؛ أي أنها مجموعة متسلسلة من التعليمات البرمجية التي يتبعها المفسِّر حَسَبَ ورودها (من الأعلى إلى الأسفل).
وفي هذا الفصل نعرف أن بايثون لغة إجرائية (Procedural)؛ وهذا يعني تركيب البرنامج من إجراءات يستدعي كل واحد منها مجموعة أخرى من الإجراءات. فقد يستدعي الإجراء الأول إجرائين، وقد يستدعي كل منهما إجرائين كذلك، …إلخ. حتى تعود النتيجة إلى الإجراء الأوَّل الذي يمثِّل مدخل البرنامج: main. ويتشكل لدينا التسلسل الهرمي كما هو موضح:
فأما الإجراء (Procedure) فهو: قطعة نص برمجي مخزَّنة تُستدعى باسمها بعوامل متغيرة. ويسمى الدالة (Function) أو الروتين (Routine) أو البرنامج الفرعي (Sub-Program). ونقول استدعي الإجراء (Call) أو نفذه (Execute). وتسمى مكان القطعة التي قامت بالاستدعاء: موقع الاستدعاء (Call-site).
أما القطعة الأخيرة: if __name__ == "__main__" فإن المتغير __name__ هو متغير مخصوص في لغة بايثون تعطيه القيمة __main__ إذا تم تشغيل البرنامج عن طريق هذا الملف، بخلاف ما لو تم استيراد هذا الملف. وسيأتي بيان ذلك في الفصل التالي، عندما نشرح الحزم والوحدات.
فأي إجراء يتم تعريفه؛ كالمتغير الذي يتم تعريفه: هو نص برمجي محفوظ ينتظر الاستدعاء حتى يحضر في ذاكرة البرنامج في ظرف تنفيذي ويتم تشغيله بعوامل معيَّنة. لذا فإننا إن لم نشتغل الإجراء الأوَّل main فإن البرنامج وإن كان يحفظ هذه الإجراءات إلا أنها تحتاج إلى الاستدعاء لتعمل.
تعريف الإجراء
يعرَّف الإجراء بـ def ويتكون من قسمين:
الأول: الحد (Function Signature). وهو ثلاثة أجزاء:
الاسم (Name): الذي يُطلَبُ به
العوامل (Parameters): وهي متغيرات تؤثر (تعمل) في النتيجة
نوع العائد (Return Type): نوع القيمة التي يرجع بها الإجراء نتيجة عمله
الثاني: الجسد (Body). وهو القطعة البرمجية التي جُعِلَ اسمه عنوانًا لها. وقد يتضمن الجسد جملة رجوع (Return Statement) للخروج بنتيجة
تأمل المثال التالي:
def add(x, y): result = x + yreturn result
الحد: def add(x, y)
الاسم: add
العوامل: x, y ونفترض أنهما من النوع العددي Number
العائد: Number (نوع النتيجة)
الجسد:
result = x + y هنا نرى كيف أن العوامل أُعمِلَت في الإجراء
return result تسمى جملة الرجوع وهي التي؛ ترجع بالنتيجة إلى الموضع الذي طلب الإجراء.
التصريح بالأنواع
تسمح بايثون بعدم التصريح بأنواع المتغيرات إلا أنه يجوز، وذلك على النحو التالي:
from numbers import Numberdef add(x: Number, y: Number) -> Number: result = x + yreturn result
وبهذا نعرف أن التصريح بنوع المتغيِّر يكون بإضافة نقطتين رأسيتين (:) ثم النوع Number بعد كل عامل.
وبعد السهم لنوع الناتج من الإجراء (->)
ومن الأنواع المبنيَّة (Built-in Types) في بايثون:
Number وهو النوع الذي ترجع إليه جميع أنواع الأعداد
int الأعداد الصحيحة، نحو: 10
float الأعداد العشرية، نحو: 10.5
str وهي نوع النص، نحو: "Salam"
list قائمة وإن شئت تحديد نوع العنصر الواحد فيها؛ فإنك تضعه بين القوسين المربعين، نحو:
وعند طلب التنفيذ نعين العوامل. فينتج لنا بطلب calculate_grade(95) نسخة معيَّنة من تفاصيل الإجراء، نسمّيها ظرف التنفيذ (Execution Frame) ؛ يكون فيه المتغير score=95 تكون هذه النسخة حاضرة في ذاكرة البرنامج وقت تنفيذ الإجراء:
قد تكون العوامل كثيرة في الإجراء على النحو التالي:
def weather_condition(temperature, humidity, wind_speed):if temperature >=30and humidity >=60and wind_speed >=10:return"Rainy"elif temperature >=20and humidity >=50and wind_speed >=5:return"Cloudy"elif temperature >=10and humidity >=30and 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!
العوامل الجائزة
الأصل في العوامل المعرَّفة الوجوب؛. فلو أهملت أحدها فإنك ستواجه بالخطأ:
تقول رسالة الخطأ (السطر الأخير) أن الإجراء يفتقد عاملين موضعيين إلزاميين، وهما: humidity و wind_speed.
فلو أردنا أن يكون عمل الإجراء بحسب أحد العوامل بالتعيين، على نحو:
لو عينت السلزيوس فالتحويل لفهرنهايت: convert_temperature(celsius=32)
لو عينت الفهرنهايت فالتحويل لسلزيوس: convert_temperature(fahrenheit=89.6)
وإليك معادلة التحويل بين نوعيْ درجة الحرارة:
\[
F = \frac{9}{5} C + 32
\]
فأي عامل نعرفه بقيمة ابتدائية فإن بايثون تعتبره اختياريًّا، ولو بالقيمة العدميَّة None. فنعرِّفُ العوامل بقيَم عدميَّة، ونفحص وجودها بالشرط is not None لنُعمِلَها أو نهملها:
فنتوقع وقوع خطأ هنا لأن bmi غير معرفة إلا في نطاق الإجراء:
print(bmi)
---------------------------------------------------------------------------NameError Traceback (most recent call last)
Cell In[12], line 1----> 1print(bmi)
NameError: name 'bmi' is not defined
تقول رسالة الخطأ (السطر الأخير) أن المتغير bmi غير معرَّف. وهذا منطقي لأن النطاق الخارجي لا يعلم ما تكنه النطاقات الداخلية الخاصة بالإجراءات. وهو أمر مطلوب جدًّا ومرغوب في البرمجة. وذلك يعني أننا لن نتعب كثيرًا في اختيار الأسماء داخل كل إجراء، مخافة التعارض.
عوامل غير مصرح بها
وعلى العكس فإن المعرَّفات الخارجة معروفة في الداخل؛ وذلك يعني أنها يمكن أن تعمل في الإجراء بشكل غير مباشر. أي أنها عوامل غير مصرَّح بها (هو: max_length في هذا المثال):
نصيحة: اجعل الاستعمال تاليًا لأخص نطاق تسلم. فهو كاستعمال الضمائر في اللغة: لا نحب أن تبعُد عما أبدلت عنه؛ إذ ذلك مظنَّة الإبهام. لكن قد يكون من المعرَّفات ما هو ثابت ومعمولٌ به في كل مكان. فهو أشبه بالإعدادات أو الضبط للبرنامج كله. ومثل ذلك أمره سهل.
ثمرة الإجراء
تُخرج الإجراءات نتائج معالجتها بإحدى طريقتين:
الأولى: الرجوع: حيث ترجِع النتيجة بجملة return لموضع طلب الإجراء، وغالبًا ما يتم تعيينه لمتغير، نحو: y = sqrt(x).
الثانية: المفعول: نحو: list.sort(xs) حيث يعدِّل الإجراء العامل الذي تم تمريره إليه؛ ولذلك نسميه مفعولا به.
وذلك مثل print() فإن أحد عواملها غير مذكور (وهو المفعول)، وهو: file=sys.stdout ولأجله تكتب النتيجة على الشاشة.
الإجراء الخاوي
والإجراء دائمًا يرجع بنهاية آخر جملة فيه، لكن الذي لا يصرَّحُ فيه بكلمة الرجوع return تُقدَّر له القيمة العدمية: None ويسمى الإجراء حنيها خاويًا (void).
فائدة: إذا رأيت إجراءً خاويًا فاعلم أن له مفعولاً، سواء من عوامله المباشرة أو غير المباشرة. والسبب في ذلك أنه لا بد للإجراء من أن يُثمر، وإلا فالإجراء الذي ليس له أثر، فإن وجوده كعدمه.
تأمل المثال التالي الذي يأخذ عوامل ثم لا يرجع بشيء، لكنه يُظهر النتيجة على الشاشة:
قد تتساءل كيف تعمل print وهي تأخذ عددًا لا محدودًا من العوامل؟ حتى نجيب عن ذلك ننظر في عملية فك الأقواس وهي علامة النجمة * قبل المتغيِّر المشير لمجموعة، على النحو التالي:
xs = [10, 20, 30]print(xs)print(*xs)
[10, 20, 30]
10 20 30
لاحظ الفرق بين الطلب الأول والثاني:
الأوَّل يطبع المتغير xs ككل، أي: كقائمة من ثلاثة أعناصر
يُمرر واحدًا تلوَ الآخر للإجراء print بعد فك الأقواس كأنك كتبت: print(10, 20, 30)
وحين تستعمل علامة النجمة في تعريف عوامل الإجراء، فإن أي عامل موضعي زائد عن العدد، يكونُ فيه كصفّ (وسيأتي الكلام عن الصف في باب المجموعة المرتبة). وبالمثال يتضح المقال. وقد اصطُلح على تسميته args وهي اختصار كلمة Arguments: