مارس 05، 2010

سلسلة بايثون للمبتدئين - 06

سنتطرق اليوم إلى موضوع الدوال (functions) في بايثون. لكن أولا ما هي الدوال و لماذا نحتاجها ؟

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

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

إذا كانت لديك خلفية برمجية بسيطة فقد سمعت عن المهام (procedures) و هي شبيهة بالدوال إلى حد كبير. في الواقع الفرق بين المهام و الدوال هو أن هذه الأخيرة بعد الإنتهاء من عملها تخبر بالنتيجة التي توصلت إليها بينما المهام لا تفعل ذلك. في بايثون لا وجود للمهام، فقط الدوال. المهام في بايثون هي دوال تنتهي بنتيجة None.

بايثون كباقي لغات البرمجة يضم العديد من الدوال المدمجة كـ max, min, sum و غيرها حتى يوفر على المبرمج عناء تطوير كل شيء من الصفر و يعينه على التركيز في المهمة التي هو بصدد إنجازها.

في بايثون هنالك نوعان من الدوال، الدوال العادية و الدوال المجهولة الإسم (anonymous functions).


الدوال في بايثون :

لتحديد دالة نستخدم الكلمة المفتحية def يليها إسم الدالة ثم علامة : . الإسم يكون من إختيارنا و يفضل أن يكون له صلة بمضمون الدالة و ذلك حتى يسهل فهم البرنامج لاحقا سواءا منا أو فريق العمل الذي تعمل معه.
 مثال:
def  calc():
    x = 10
    y = 6
    return x + y


print calc()

في هذا المثال قمنا بتحديد/إنشاء دالة بإسم calc(). لاحظ جيدا أن إضافة القوسين و : بعد إسم الدالة إجباري.
داخل الدالة قمنا بتحديد المتغيرة x التي أسندنا لها قيمة 10، و المتغيرة y أسندنا لها العدد 6.
بعد ذلك سنرجع مجموع x و y باستخدام الكلمة المفتحية return
بعد كتابة الدالة قمنا باستدعائها من عن طريق print التي ستعرض على الشاشة النتيجة التي توصلت بها من الدالة بعد انتهاء هذه الأخيرة من عملها.

الآن ماذا لو أردنا من الدالة calc() أن تحسب قيمة x و y مختلفة عما هي عليه الآن؟ هل نكتب دالة جديدة لحساب كل قيمتين جديدتين؟ الجواب هو لا. يكفي فقط تعديل الدالة السابقة من خلال استخدام ما يعرف بالمعايير (arguments).

المعايير يمكن إعتبارها كحقول يمكن ملئها بمعلومات أو قيم لكي تستخدمها الدالة لإنجاز مهمتها.

الدالة السابقة ستصبح كما يلي بعد إستخدام المعايير:
def  calc(a, b):
    x = a
    y = b
    return x + y


print calc(10, 3)
print calc(20, 80)

التغييرات التي طرأت هي كما يلي:
في السطر الأول أضفنا معيارين هما a و b مفصولين بعلامة ,
القيمة المسندة للمتغيرة x هي قيمة المعيار a و قيمة المتغيرة y هي قيمة المعيار b
عند المناداة على الدالة calc() يتم إسناد العدد 10 كقيمة للمعيار a و العدد 3 كقيمة للمعيار b
و ليتضح المثال نادينا عليها مرة ثانية بقيمتين مختلفتين، و بالطبع ستكون النتيجة الثانية مختلفة عن الأولى :)

طبعا بايثون يقدم مرونة أكثر تتمثل في:
إمكانية إستخدام معايير بقيمة مسبقة أو إفتراضية،
إمكانية إستعمال معيار بقيم متعددة،
إمكانية إستخدام معيار متعددة على شكل قواميس.

* مثال لدالة calc() بإستخدام معيار ذو قيمة محددة مسبقا:

def calc(a, b=10):
    x = a
    y = b
    return x + y


print calc(50)
print calc(30, 60)

لاحظ معي أن عند كتابة المعيار b أضفنا له =10 و هذا يعني أن قيمته الإفتراضية تساوي 10.
في السطر الأول الذي ينادي على calc() كتبنا قيمة واحدة و مع ذلك كانت النتيجة هي 60.
ثم عاودنا تشغيل الدالة بمعيارين فكانت النتيجة هي 90 بدلا من 40 و هذا يعني أن القيمة الإفتراضية للمعيار b تم تبديلها بالقيمة المدخل أثناء تشغيل الدالة.


* مثال لدالة calc() بإستخدام معيار ذو قيم متعددة:
def calc(a, *b):
    x = a
    y = 0    
    for i in b:
        y = y + i

    return x + y


print calc(10, 15, 18, 17, 40)

لاحظ جيدا أن المعيار الثاني مسبوق بـ * و هو ما سيجعله قابل لإستيعاب أكثر من قيمة.
لإستخراج كل القيم من المعيار b إستخدمنا الحلقة التسلسلية for. في كل دورة نضيف مجموع المتغيرة y و i على القيمة السابقة ل y
عند إنتهاء الحلقة for ستكون قيمة المتغيرة y هي مجموع كل قيم المعيار b

لاحظ جيدا أنه رغم تحديد معيارين فقط للدالة calc() فقد كتبنا خمسة قيم عند تشغيلها. القيمة 10 سيحتفظ بها داخل المعيار a و 15,18,17,40 ستحفظ داخل معيار واحد ألا و هو b


مثال لدالة calc() بإستخدام معايير متعددة على شكل قاموس:
def calc(a, **b):
    x = a
    y = 0
    
    for k, v in b.items():
        print k, "=", v
        y = y + v

    return x + y

print calc(10, t=15, r=18, u=17, g=40)

لاحظ جيدا أن المعيار الثاني مسبوق بـ ** و هو ما سيجعله قابل لإستيعاب أكثر من معيار و قيمته.
هنالك إختلاف بسيط بين هذا المثال و المثال السابق. بالنسبة للحلقة for إستخدمنا المتغيرة k و v للتعبير عن المفتاح و قيمته (راجع التدوينة الخاصة بالقواميس) بالنسبة للمعيار القاموس b. في كل دورة يتم عرض إسم المفتاح المعبر عنه ب k و قيمته المعبر عنه ب v. و بعدها يتم إضافة قيمة v على المجموع السابق ل y.
عند تشغيل الدالة أدخلنا قيمة المعيار a و هي 10 ثم بعدها حددنا أسماء المعايير و قيمتها تماما كما نفعل عند إنشاء الدوال. هذه الميزة تسمح بإنشاء دوال غاية في المرونة.


الدوال المجهولة الإسم :

دعم الدوال المجهولة (أو الغير المعرفة) يعتبر من مزايا لغات البرمجة الحديثة التي تحاول تقديم مختلف أساليب التعبير في البرمجة. الدوال المجهولة تدخل في أساسيات البرمجة الدلالية/الوظيفية (functional programming) و هو أسلوب آخر في البرمجة يرتكز الدوال في كل شيء، يمكن إعتبار هذا النوع من البرمجة أكثر ملائمة لمتخصصي الرياضيات. يمكن إعتبار لغة Haskell من أشهر اللغات الوظيفية.

بايثون يقدم دعم لا بأس به للبرمجة الدلالية و ذلك يتمثل بالدرجة الأولى في lambda ثم  map.

لنقم بإعادة صياغة الدالة calc() بإستخدام lambda
f = lambda x, y: x + y
print f(10, 20)

أول شيئ يلاحظ هو تقلص للعدد السطور. جميل أليس كذلك؟ لنشرح السطر الأول.
نقوم بتحديد دالة مجهولة الإسم مستخدمين كلمة lambda يليها معيار x و y. بعد ذلك نضيف : للفصل ثم نكتب كيف سيعمل المعيارين. في مثالنا نقوم بجمع x و y. ثم نقوم بإسناد هذه الدالة إلى f لتصبح بذلك ممثلة للوظيفة.
في السطر الثاني تعرض print قيمة النتيجة التي توصلت إليها f بإستخدام قيمة المعيارين x و y

قد تظهر الدوال المجهولة الإسم و كانها صعبة و قد تكون كذلك في البداية لكنها عملية جدا.

مثال آخر:
say_hi = lambda name: "Hi " + name
friends = ["Ayman", "Karim", "Nour", "Youness"]
for friend in friends:
    print say_hi(friend)

في هذا المثال قمنا بتحديد دالة lambda بمعيار واحد و هو name و الذي ستضيف إليه كلمة "Hi " حتى تظهر على شكل تحية بالإنجليزية. هذه الدالة يعبر عنها ب say_hi
ثم نكتب قائمة بإسم friends نسند إليها كل أسماء أصدقائنا.
و لنحي كل واحد منهم علينا أن نستخدم الحلقة التسلسلية for. في كل دورة يتم إستدعاء say_hi و تسند إليها قيمة (عبارة إسم صديق) مختلفة.

المثال السابق يختصر علينا سطرين أو ثلاثة لو كتبناه بإستخدام الدوال العادية، و رغم ذلك يمكن إختصاره مجددا ليصبح في سكرين بإستخدام الدالة map. إليكم المثال:
friends = ["Ayman", "Karim", "Nour", "Youness"] 
print map(lambda name: "Hi " + name, friends)
ما توفره دالة map هو إمكانية كتابة كود lambda كالمعيار الاول لـ map ثم القائمة friends كمعيار ثاني. بعد ذلك تقوم print بعرض النتيجة.

و يمكن إختصار السطرين السابقين في سطر واحد، إليكم المثال:
print map(lambda name: "Hi " + name, ["Ayman", "Karim", "Nour", "Youness"])

طريقة عمل map() هي كالتالي: كل قيمة في القائمة يتم تمريرها كمعيار للدالة. map شبيهة بالحلقة for لكنها تبقى متميزة في الإستخدامات مع الدوال و ربطها بالقوائم.


تريد تعلم المزيد؟
إليك موضوع الصديق أحمد يوسف بخصوص الدوال أو كتاب بايثون.


ملاحظة: بعد التعريف بالدوال و طرق إستخدامها سأطرح مشاكل جديدة للمشروع يولر Euler Project.

ليست هناك تعليقات:

إرسال تعليق