Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
AhmadAlsaadi
GitHub Repository: AhmadAlsaadi/Arabic-python-notebook
Path: blob/master/الفصل السادس-كتابة الدوال.ipynb
675 views
Kernel: Python 3 (ipykernel)
import style style._set_css_style("custom.css")

الفصل السادس : كتابة الدوال


في هذا الفصل سوف نتطرق الى معرفة الدوال والتي هي عبارة عن أكود برمجية مستقلة يمكن استدعاءها متى واينما شاء المبرمج. الغرض الأساسي من استخدامها يكمن في تجزئة البرنامج الى أجزاء صغيرة تساعد المبرمج على عدم تكرار الأكواد داخل البرنامج وتسهل عليه عملية إصلاح الأخطاء.

أهداف الفصل

عند إتمام هذا الفصل يجب أن يكون لديك إلمام بالآتي:

  1. كيفية كتابة دالة وطريقة استدعائها.
  2. التعرف على أنواع مدخلات الدوال وكيفية استخدامها.
  3. التعرف على الدوال الداخلية وخواصها
  4. التعرف على مخرجات الدوال.
  5. تعلم الطريقة المختصرة لكتابة الدوال عن طريق استخدام lambda
  6. التعرف على الطريقة الصحيحة لكتابة تعليقات إرشادية تشرح كيفية استخدام الدوال.

تعريف الدوال

لكتابة دالة في بايثون نستخدم def في بداية السطر لتبليغ مفسر بايثون بأننا نرغب في تعريف دالة جديدة ثم نتبع def بإسم الدالة الذي ينتهي بقوسين اعتياديين () يوضع داخلهما مدخلات الدالة وفي نهاية السطر نكتب نقطتين فوق بعض كما في المثال التالي:
def function_name(): pass
لاحظ أن في الدالة السابقة كتبنا اسم الدالة ليكون function_name وبإمكانك أن تستخدم أي اسم آخر شريطة أن تنطبق عليه شروط كتابة أسماء المتغيرات التي تعرفنا عليها في الفصل الثاني. كما أننا في المثال السابق لم نقم بوضع أي مدخلات داخل قوسي الدالة لأن ذلك الأمر اختياري بحسب إحتياج الدالة للمدخلات. وأخيراً فإن كلمة pass والتي تعنى أن الدالة تم تعريفها فقط و لا تقوم بعمل أي شيء حتى الآن. ولكي نجعل الدالة تقوم بعمل ما فإننا نقوم بكتابة كود برمجي ليحل محل كلمة pass كما في المثال التالي:
def hello(): print("hello I am inside a function")
لاحظ أيضاًً أن الكود البرمجي داخل الدالة يكتب بعد ترك مسافة على الأقل بمقدار حرف واحد والمتعارف عليه بين مبرمجي بايثون هو ترك مسافة بمقدار أربعة أحرف. كما يجب التذكير بأن جميع أسطر الكود البرمجي داخل الدالة يجب أن تترك نفس المسافة وإلاّ سوف يعطي مفسر بايثون إشعارا بوجود خطأ.

استدعاء الدوال

لا يتم تنفيذ الكود داخل الدالة إلاّ بعد استدعائها وذلك بكتابة اسم الدالة متبوعا بقوسين بداخلهما مدخلات الدالة إن كان هناك مدخلات للدالة أما إذا لم يكن هناك مدخلات للدالة فيكتفى بكتابة قوسين فارغين كما في المثال التالي

hello()
hello I am inside a function
عند تشغيل الكود في المثال أعلاه سوف يقوم مفسر بايثون بالبحث في ذاكرة الكمبيوتر عن دالة اسمها hello ليدخل اليها وينفذ ما بداخلها من أكواد برمجية وإذا لم يجد تعريفاً لها فسوف يعطي رسالة تفيد بعدم وجود تعريف بهذا الاسم كما في المثال التالي:
myinfo()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) /tmp/ipykernel_28909/2987341299.py in <module> ----> 1 myinfo() NameError: name 'myinfo' is not defined

لذلك يمكن كتابة تعريف الدالة في أي مكان من البرنامج شريطة أن لا يسبق استدعاءها تعريفها عند كتابة البرنامج. لاحظ أيضاً أن في الأمثلة السابقة كتبنا قوسي الدالة فارغين لأن الدالة لاتتطلب أي معلومات إضافية لتنفيذ الكود البرمجي داخلها.

مدخلات الدوال

لنفرض الآن أننا نريد كتابة دالة أكثر ذكاءً بحيث تقوم بالقاء التحية على أي شخص ندخل اسمه للدالة. للقيام بهذه المهمة يمكن كتابة الدالة بالطريقة التالية

def hello(name): print("Hello ", name)
لاحظ عند كتابتنا للدالة السابقة قمنا باعطاء اسم الشخص المراد القاء التحية عليه متغير اسميناه name ووضعناه داخل قوسي الدالة. ومن ثم استخدمنا هذا المتغير في كتابة أمر الطباعة. يسمى هذه المتغير مدخل الدالة. وعند استدعاء الدالة ما علينا القيام به هو إدخال اسم الشخص المراد القاء التحية عليه كنص كما في المثلة التالية:
hello("Ahmad")
hello("Ali")
hello("Sara")
كما يمكن كتابة دالة باكثر من مدخل يفصل بينها بفاصلة داخل قوسي الدالة كما في المثال التالي:
def addition(x,y): print("مجموع الرقمين هو:", x+y) addition(3,4)

أنواع مدخلات الدوال

يجب الإشارة الى أن مدخلات الدالة تنقسم الى ثلاثة أقسام. القسم الأول يسمى مدخلات الزامية بحيث أنه متى ما تم كتابتها داخل قوسي الدالة عند تعريفها فإن الدالة لا يمكن استدعاءها إلاّ بعد تزويدها بهذه المدخلات كما في المثال التالي:
def power(a): print(a**2) power()

تفيد رسالة الخطأ السابقة أنه تم تعريف الدالة power بمتغير الزامي a لكن هذا المتغير كان مفقوداً عند استدعاء الدالة. وعندما نزودها بهذه القيمة فإن الدالة تفنذ بشكل طبيعي كما في المثال التالي:

power(3)

والقسم الثاني من مدخلات الدالة يسمى مدخلات اختيارية. بحيث تعطى هذه المدخلات قيم افتراضية عند تعريف الدالة باستخدام علامة اليساوي "=". وعند استدعاء الدالة يمكن للمبرمج استبدال القيم الافتراضية بقيم جديدة والا قام مفسر بايثون باستخدام القيم الافتراضية التي تم كتابتها داخل قوسي الدالة وقت تعريفها كما في المثاليين التاليين:

def power(a=4): print(a**2) power()
power(3)

ولكي نوضح الفرق بين هذين االنوعين من المدخلات للننظر للمثال التالي:

def addition(x,y=4): print(x+y)
يعتبر المدخل x إلزامي بينما المدخل y اختياري وذلك لأننا قمنا باعطائه قيمة افتراضية هي العدد 4.
الآن إذا أردنا أن نستدعى الدالة السابقة بدون مدخلات فان مفسر بايثون سوف يعطينا إشعارا بوجود خطأ مفاده أن مدخلاً الزامياً واحدأ مفقود وليس مدخلين كما في المثال التالي:
addition()
لنقوم الآن بااستخدام مدخل واحد لاستدعاء الدالة كما في المثال التالي:
addition(2)
المدخل الذي استخدمناه في المثال السابق هو المدخل الإلزامي والذي يمثل قيمة x أما المدخل الاختياري فقد قام مفسر بايثون باستخدام قيمته الافتراضية (4) عندما اهملنا ادخالها وقت استدعائنا للدالة. إذا رغبنا في تغيير القيمة الافتراضية للمدخل y فإننا نقوم بإدخال القيمة وقت استدعاء الدالة بعد إدخال قيمة x أولاً كما في المثال التالي:
addition(2,9)
إن ترتيب إدخال قيم المدخلات عند استدعاء الدالة مهم جدا ففي المثال السابق المدخل x أخذ القيمة 2 والمدخل y أخذ القيمة 9 وذلك بحسب الترتيب المتبع عند تعريف الدالة. يجب الإشارة الى أنه إذا تم استخدام اسم المدخل عند استداعاء الدالة فان ترتيب المدخلات يصبح غير مهم كما في المثال التالي:
addition(y=8,x=1)
ففي المثال السابق تم إدخال قيمة y قبل قيمة x ولكن مفسر بايثون تعرف على قيم كل مدخل عندما زودناه بأسماء المدخلات لذلك أصبح ترتيب المدخلات غير مهم. يجب أن نشير الى أن مفسر بايثون لا يسمح بتعريف دالة تحتوي على مدخلات إلزامية ومدخلات اختيارية إلاّ بترتيب معين وهو البدء بالمتغيرات الإلزامية أولاً ثم المدخلات الاختيارية ثانياً. وعند تعريف دالة بعكس هذا الترتيب فإن مفسر بايثون يعطي اشعارا بوجود خطأ مفاده ان مدخلاً الزاميًا أُدخل بعد مدخلٍِ اختياري كما في المثال التالي:
def addition(y=4,x): print(x+y)
File "/tmp/ipykernel_28940/4098089560.py", line 1 def addition(y=4,x): ^ SyntaxError: non-default argument follows default argument

القسم الثالث من مدخلات الدالة يسمى المدخلات المرنة. ما ذا لو أننا أردنا كتابة دالة بحيث نعطي المستخدم حريته في اختيار عدد المدخلات التي يود معالجتها كأن تكون لدينا دالة تقوم بحساب حاصل ضرب القيم التي ندخلها لها بحيث يمكن أن يكون عدد القيم المدخلة 2 أو 3 أو 4 أو أي عدد من القيم الذي يختاره المستخدم فماذا عسانا أن نعمل.

لم يغفل مبرمجوا لغة بايثون عن هذه الخاصية ووضعوا تركيباً يمكّن مفسر بايثون من تعريف دالة ٍ مرنة المدخلات الإلزامية باستخدام علامة النجمة قبل اسم المدخل كما هو موضح في المثال التالي:

def mult(*num): product=1 for n in num: product=product*n return product mult(2,3)
6
mult(2)
2
mult(2,3)
6
mult(1,2,3,4,5)
120

يجب ملاحظة ان المدخل الألزامي المرن يتم التعامل معه على أنه قائمة أو مصفوفة كما هو موضح في المثال السابق.

كما يمكن تعريف دالة بمدخلات اختيارية مرنة من خلال وضع علامتي نجمة أمام المتغير الذي سوف يمثل عدد المتغيرات الاختيارية كما في المثال التالي:

def total_fruits(**fruits): total = 0 for amount in fruits.values(): total += amount return total total_fruits(banana=5, mango=7, apple=8)
20
total_fruits(banana=5, mango=7, apple=8, oranges=10)
30
total_fruits(banana=5, mango=7)
12

يجب ملاحظة أن المدخلات الاختيارية المرنة يتم التعامل معها على أنها قاموس كما هو موضح في المثال السابق.

مخرجات الدوال

لنفرض الآن أننا لا نريد طباعة ما تقوم به الدالة على الشاشة ولكن نريد فقط إرجاع ما نتج من عمليات داخل الدالة ليكون قيمة لمتغير بحيث نستطيع ادخال هذه القيمة في عمليات أُخرى. لكي نقوم بهده المهمة لابد لنا من استخدام العبارة return لإخبار مفسر بايثون بأن الدالة تود إرجاع محصلة الكود البرمجي داخلها الى متغير آخر كما في المثال التالي:
def addition(x,y): return x+y sum=addition(3,7) print(sum**2)
في المثال السابق تم اسناد ناتج الدالة الى المتغير sum ومن ثم قمنا بطباعة قيمة المتغير بعد تربيعها.
في حقيقة الأمر إن الدالة دائماً تقوم بإرجاع محصلتها حتى لو لم نكتب العبارة return. فإذا لم نستخدم العبارة return فإن الدالة تقوم بإرجاع قيمة افتراضية هي None وتعني أنه لم يتم إرجاع أي شيئ. كما في المثال التالي:
def addition(x,y): z=x+y sum=addition(5,9) print(sum)
لاحظ أن الدالة السابقة قامت بجمع المدخلين وإسناد النتيجة الى المتغير z. وبما أننا لم نستخدم الأمر return لإرجاع قيمة المتغير z فان الدالة قامت بإرجاع القيمة الافتراضية None. هذي النقطة تقودنا الى ما يسمى بمحال المتغيرات داخل الدوال.

مجال المتغيرات داخل الدوال

المتغير z في الدالة السابقة تم مسحه من الذاكرة بمجرد أن تم الإنتهاء من تنفيذ الدالة لذلك تسمى هذه المتغيرات بالمتغيرات المحلية أما المتغيرات التى تقع خارج الدوال فتسمى متغيرات عالمية (global variables) بحيث تحتفظ بقيمتها في أي مكان من البرنامج حتى لو كانت في داخل الدوال كما في المثال التالي

x=5 def addition(): print(x) addition()
5
لكن هناك أمر مهم يجب أن تعرفه وهو أنه لا يمكننا تغيير قيم المتغيرات العالمية داخل الدوال إلاّ بعد استخدام كلمة global قبل اسم المتغير المراد تغيير قيمته داخل الدالة كما في المثال التالي:
x=5 def change_x(): global x x=10 change_x() print(x)
لاحظ أن كلمة global في المثال السابق أبلغت مفسر بايثون بأن هذا المتغير عالمي ويجب الإحتفاظ بقيمته حتى بعد الإنتهاء من تنفيذ الدالة. أما إذا لم نستخدم كلمة global فان مفسر بايثون سوف يفترض أن x متغير محلى غير المتغير x الذي يقع خارج الدالة وسوف يمحو قيمته من الذاكرة بعد الإنتهاء من تنفيذ الدالة ولا يحدث أي تغيير للمتغير الواقع خارج الدالة كما في المثال التالي:
x=5 def change_x(): x=10 change_x() print(x)

الدوال الداخلية

كما يجب الإشارة الى أنه بالإمكان إستدعاء دالة من داخل دالة كما في المثال التالي:

def inner(): print("call me from inside") def outer(): inner() outer()
call me from inside

وكذلك يمكن تعريف دالة داخل دالة ولكن يجب استدعاء الداخلية من داخل الدالة الأم وإلاّ سوف يتم مسح تعريف الدالة الداخلية من ذاكرة الكمبيوتر بعد الإنتهاء من تنفيذ كود الدالة الأم كما في المثالين التاليين:

def outer(): def inner(): print("I am inside inner") inner() outer()
I am inside inner
def outer2(): def inner2(): print("I am inner2") outer2() inner2()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) /tmp/ipykernel_28940/2232286928.py in <module> 3 print("I am inner2") 4 outer2() ----> 5 inner2() NameError: name 'inner2' is not defined

في المثال الأول تم تعريف الدالة inner داخل الدالة outer وتم استدعاء الدالة inner من داخل الدالة الأم outer فتم تنفيذ الكود البرمجي الذي يقع داخل الدالة الداخلية inner. في المثال الثاني تم تعريف الدالة الداخلية inner2 داخل الدالة الخارجية outer2 لكن عملية الإستدعاء تمت من خارج الدالة الأم لذلك عندما انتهى مفسر بايثون من تنفيذ الدالة الأم التي بداخلها تعريف الدالة الداخلية inner2 تخلص مفسر بايثون من جميع المتغيرات المحلية ومن ضمنها تعريف الدالة inner2. لذلك عندما قمنا باستدعاء الدالة الداخلية inner لم يجد مفسر بايثون أي ا ثر للدالة الداخلية وأعطانا رسالة تفيد بعدم وجود المتغير inner2.

يمكن تجاوز هذه المشكلة بحيث يمكننا استدعاء الدالة التي تم تعريفها من داخل دالة اخرى بتكون متغير عالمي داخل الدالة الأم ومن ثم نقوم باسناد الدالة الداخلية لهذا ا لمتغير بحيث يمكننا استدعائها من خارج الدالة الأم كما في المثال التالي:

def outer3(): global inner3 def inner3(): print("I am inside inner3") outer3() inner3()
I am inside inner3

كما يجب الإشارة الى أنه بالإمكان ارجاع الدالة الداخلية كناتج للدالة الخارجية باستخدام العبارة return كما هو موضح في المثال التالي:

def outer3(): global inner3 def inner3(): print("I am inside inner3") return inner3 inner3=outer3() inner3()
I am inside inner3

ويمكن كتابة المثال السابق بطريقة اخرى كما في المثال التالي:

def outer3(): global inner3 def inner3(): print("I am inside inner3") return inner3 outer3()()
I am inside inner3

الطريقة المختصرة لكتابة الدوال

بالإضافة الى الطريقة التلقيدية التي تناولنهاها بشيئ من التفصيل هناك طريقة مختصرة لكتابة الدوال باستخدام العبارة lambda كما هو موضح في المثال التالي:

f=lambda x: x**2 f(4)
16

فالتركيب اللغوي لكتابة الدوال المختصرة يبدأ بكتابة العبارة lambda متبوعاً بالمدخلات التي سوف تدخل في العمليات التي سوف تجريها الدالة. تنتهي المدخلات بنقطتين فوق بعض يكتب بعدها العمليات التي يود المبرمج إجراءها داخل الدالة. يمكن هذا التركيب من كتابة الدالة بدون اسم فيطلق عليها احياناً الدالة الغامضة anonymous function ويمكن ايضاً ان يسند هذا التركيب لمتغير يشير الى هذه الدالة كما فعلنا في المثال السابق باسناد الدالة التربيعية الى المتغير f. ويتم تنفيذ الدالة المختصرة بنفس الطريقة التقليدية التي تعلمناها سابقاً بأن توضع المدخلات بين قوسين بعد اسم المتغير الذي يشير الى الدالة المختصرة.

ويمكن التأكد من أن المتغير f أصبح يشير الى دالة باستخدام الدالة type كما هو موضح في المثال التالي:

type(f)
function

لاستخدام أكثر من مدخل في الدالة المختصرة يفصل بين المدخلات بفاصلة كما تعلمنا سابقاً كما هو موضح في المثال التالي:

f=lambda x,y:x**2+y**2 f(4,3)
25

لاحظ أن ترتيب المدخلات وقت تنفيذ الدالة مهم كما تعلمنا سابقاً مع الدوال التقليدية.

التعليقات الارشادية للدوال

ينص دليل بايثون الارشادي رقم PEP 8 الخاص بتوحيد طريقة تنسيق وكتابة الأكواد البرمجية وجعلها سهلة القراءة لكافة المبرمجين على أنه يجب كتابة ملخص على شكل تعليق أو ملاحظة في السطر الذي يتلو اسم الدالة بحيث يشير فيه المبرمج على الوظيفة التي تقوم بها الدالة كما في المثال التالي:
def add_two_numbers(x,y): '''تقوم هذه الدالة بحساب مجموع الرقمين المدخلين وارجاع حاصل جمعها''' return x+y
يمكن التعرف على التعليق الذي كتبه المبرمج عن دالته التي كتبها باستخدام الدالة ثنائية الشرطة السفلية __doc__ بعد اسم الدالة التي نود معرفة معلومات حولها كما هو موضح في المثال التالي:
print(add_two_numbers.__doc__)
تقوم هذه الدالة بحساب مجموع الرقمين المدخلين وارجاع حاصل جمعها

تمارين استكشافية

1. تعرف على التعليمات الارشادية للدالة ()len التي تعلمناها سابقاً؟
1. تعرف على التعليمات الارشادية للدالة ()type ؟
1. تعرف على التعليمات الارشادية للدالة ()print ؟