محمد الميداوي

الأعضاء
  • المساهمات

    66
  • تاريخ الانضمام

  • تاريخ آخر زيارة

  • Days Won

    6

السُّمعة بالموقع

24 Excellent

3 متابعين

  1. طُوِّرَت بايثون في أواخر الثمانينات، وأُطلقَت لأول مرة عام 1991. استُوحي اسمها من المجموعة الكوميدية البريطانية Monty Python، تُعدُّ بايثون خليفة للغة البرمجة ABC متعددة الأغراض. تضمنت بايثون في إصداراتها الأولى معالجة الاستثناءات، والدوال، والأصناف والوراثة. table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } سيرشدك هذا الدرس إلى أفضل آليات وممارسات ترحيل الشيفرات من بايثون 2 إلى بايثون 3، وما إن كان عليك جعل الشيفرة متوافقة مع كلا الإصدارين. خلفية عامة صدر الإصدار 2 من بايثون عام 2000، ليُدشِّن حقبةً جديدةً من التطوير تقوم على الشفافية والشمولية، إذ شمل هذا الإصدار العديد من الميزات البرمجية، واستمر في إضافة المزيد طوال مدة تطويره. يُعد إصدار بايثون 3 مستقبل بايثون، وهو إصدار اللغة قيد التطوير حاليًا، فجاء في أواخر عام 2008، ليعالج العديد من عيوب التصميم الداخلية ويُعدِّلها. بيْد أنَّ اعتماد بايثون 3 كان بطيئًا بسبب عدم توافقه مع بايثون 2. في خضم ذلك، جاء الإصدار بايثون 2.7 في عام 2010 ليكون آخر إصدارات بايثون ‎2.x وليُسِّهل على مستخدمي بايثون ‎2.x الانتقال إلى بايثون 3 من خلال توفير قدر من التوافق بين الاثنتين، فهذا هو الهدف الأساسي من إطلاقه. يمكنك معرفة المزيد حول إصدارات بايثون والاختيار من بينها من المقالة: اعتبارات عملية للاختيار ما بين بايثون 2 و بايثون 3. ابدأ ببايثون 2.7 للانتقال إلى بايثون 3، أو لدعم بايثون 2 وبايثون 3 معًا، يجب عليك التأكد من أنّ شيفرة بايثون 2 متوافقة تمامًا مع بايثون 2.7. يعمل العديد من المطورين حصريًا بشيفرات بايثون 2.7، أمَّا المبرمجون الذي يعملون بشيفرات أقدم، فعليهم أن يتأكدوا من أنّ الشيفرة تعمل جيدًا مع بايثون 2.7، وتتوافق معه. التأكُّد من توافق الشيفرة مع بايثون 2.7 أمرٌ بالغ الأهمية لأنه الإصدار الوحيد من بايثون 2 الذي ما يزال قيد الصيانة، وتُصحَّحُ ثغراته. فإذا كنت تعمل بإصدار سابق من بايثون 2، فستجد نفسك تتعامل مع مشكلات في شيفرة لم تعد مدعومة، ولم تعد ثغراتها تُصحَّح. هناك أيضًا بعض الأدوات التي تسِّهل ترحيل الشيفرة، مثل الحزمة Pylint التي تبحث عن الأخطاء البرمجية، لكن لا تدعمها إصدارات بايثون السابقة للإصدار 2.7. من المهم أن تضع في حسبانك أنَّه رغم أنَّ بايثون 2.7 ما زالت قيد الدعم والصيانة في الوقت الحالي، إلا أنَّها ستموت في النهاية. ستجد في PEP 373 تفاصيل الجدول الزمني لإصدار بايثون 2.7، وفي وقت كتابة هذا المقال، فإنّ أجل بايثون 2.7 حُدِّد في عام 2020 (يحتمل أن تكون قد ماتت وأنت تقرأ هذه السطور :-| ). الاختبار اختبار الشيفرة جزءٌ أساسيٌّ من عملية ترحيل شيفرة بايثون 2 إلى بايثون 3. فإذا كنت تعمل على أكثر من إصدار واحد من بايثون، فعليك أيضًا التحقُّق من أنَّ أدوات الاختبار التي تستخدمها تغطي جميع الإصدارات للتأكُّد من أنَّها تعمل كما هو متوقع. يمكنك إضافة حالات بايثون التفاعلية (interactive Python cases) إلى السلاسل النصية التوثيقية (docstrings) الخاصة بكافة الدوال والتوابع والأصناف والوحدات، ثم استخدام الوحدة doctest المضمنة للتحقق من أنها تعمل كما هو موضح، إذ يعدُّ ذلك جزءًا من عملية الاختبار. إلى جانب doctest، يمكنك استخدام الحزمة package.py لتتبع وحدة الاختبار. ستراقب هذه الأداة برنامجك وتحدد الأجزاء التي تُنفِّذها من الشيفرة، والأجزاء التي يمكن تنفيذها ولكن لم تُنفَّذ. يمكن أن تطبع Cover.py تقارير في سطر الأوامر، أو تنتج مستند HTML. تُستخدم عادةً لقياس فعالية الاختبارات، إذ توضح الأجزاء من الشيفرة التي اختُبِرت، والأجزاء التي لم تُختبَر. تَذكَّر أنه ليس عليك اختبار كل شيء، لكن تأكَّد من تغطية أيّ شيفرة غامضة أو غير عادية. للحصول على أفضل النتائج، يُنصح أن تشمل التغطية 80٪ من الشيفرة. تعرف على الاختلافات بين بايثون 2 و بايثون 3 سيمكّنك التعرّف على الاختلافات بين بايثون 2 و بايثون 3 من استخدام الميزات الجديدة المتاحة، أو التي ستكون متاحة في بايثون 3. تتطرق مقالتنا حول "اعتبارات عملية للاختيار ما بين بايثون 2 و بايثون 3" إلى بعض الاختلافات الرئيسية بين الإصدارين. يمكنك أيضًا مراجعة توثيق بايثون الرسمي لمزيد من التفاصيل. عند البدء في ترحيل الشيفرة، فهناك بعض التغييرات في الصياغة عليك تنفيذها فوريًا. print حلت الدالة print()‎‎ في بايثون 3 مكان التعليمة print في بايثون 2: بايثون 2 بايثون 3 "مرحبا بالعالم!" print ("مرحبا بالعالم!")print exec تغيَّرت التعليمة exec في بايثون 2 وأصبحت دالةً تسمح بمتغيرات محلية (locals) وعامة (globals) صريحة في بايثون 3: بايثون 2 بايثون 3 exec code exec(code)‎ exec code in globals exec(code, globals)‎ exec code in (globals, locals)‎ exec(code, globals, locals)‎ / و // تُجرِي بايثون 2 القسمة التقريبية (floor division) بالعامل / ، بينما تخصص بايثون 3 العامل // لإجراء القسمة التقريبية: بايثون 2 بايثون 3 ‎5 / 2 = 2‎ ‎5 / 2 = 2.5‎ ‎5 // 2 = 2‎ لاستخدام هذين المعاملين في بايثون 2، استورد division من الوحدة __future__: from __future__ import division اقرأ المزيد عن قسمة الأعداد الصحيحة من المقالة: اعتبارات عملية للاختيار ما بين بايثون 2 و بايثون 3. raise في بايثون 3، يتطلب إطلاق الاستثناءات ذات الوسائط استخدام الأقواس، كما لا يمكن استخدام السلاسل النصية كاستثناءات: بايثون 2 بايثون 3 raise Exception, args raise Exception raise Exception(args)‎ raise Exception, args, traceback raise Exception(args).with_traceback(traceback)‎ raise "Error"‎ raise Exception("Error")‎ except في بايثون 2، كان من الصعب إدراج الاستثناءات المُتعدِّدة، لكن ذلك تغيَّر في بايثون 3. لاحظ أنَّ as تُستخدَم صراحةً مع except في بايثون 3: بايثون 2 بايثون 3 except Exception, variable:‎ except AnException as variable:‎ except (OneException, TwoException) as variable:‎ def في بايثون 2، يمكن للدوال أن تقبل سلاسل مثل الصفوف أو القوائم. أمَّا في بايثون 3، فقد أزيل هذا الأمر. بايثون 2 بايثون 3 def function(arg1, (x, y)):‎ def function(arg1, x_y): x, y = x_y expr لم تعد صياغة علامة الاقتباس المائلة `` في بايثون 2 صالحة، واستخدم بدلًا عنها repr()‎أوstr.format()‎` في بايثون 3. بايثون 2 بايثون 3 `x = `355/113`` `x = repr(355/113):` تنسيق السلاسل النصية (String Formatting) لقد تغيرت صياغة تنسيق السلاسل النصية من بايثون 2 إلى بايثون 3. بايثون 2 بايثون 3 `"%d %s" % (i, s)` `"{} {}".format(i, s)` `"%d/%d=%f" % (355, 113, 355/113)` `"{:d}/{:d}={:f}".format(355, 113, 355/113)` تعلم كيفية استخدام تنسيقات السلاسل النصية في بايثون 3 من مقالة كيفية استخدام آلية تنسيق السلاسل النصية في بايثون 3. class ليست هناك حاجة لتمرير object في بايثون 3. بايثون 2 class MyClass(object): pass بايثون 3 class MyClass: pass في بايثون 3، تُضبَط الأصناف العليا (metaclasses) بالكلمة مفتاحية metaclass. بايثون 2: class MyClass: __metaclass__ = MyMeta class MyClass(MyBase): __metaclass__ = MyMeta بايثون 3: class MyClass(metaclass=type): pass class MyClass(MyBase, metaclass=MyMeta): pass تحديث الشيفرة هناك أدَاتان رئيّسيتان لتَحديث الشيفرة تلقائيًا إلى بايثون 3 مع الحفاظ على توافقيّتها مع بايثون 2 وهما: future و modernize. تختلف آليَتا عمل هاتين الأداتين، إذ تحاول future نقل أفضل ممارسات بايثون 3 إلى بايثون 2، في حين أنّ modernize تسعى إلى إنشاء شيفرات موحدة لبايثون تتوافق مع 2 و 3 وتستخدم الوحدة six لتحسين التوافقية. يمكن أن تساعدك هاتان الأداتان في إعادة كتابة الشيفرة وتحديد ورصد المشاكل المحتملة وتصحيحها. يمكنك تشغيل الأداة عبر مجموعة unittest لفحص الشيفرة والتحقق منها بصريًا، والتأكد من أنّ المراجعات التلقائية التي أُجريَت دقيقة. وبمجرد انتهاء الاختبارات، يمكنك تحويل الشيفرة. بعد هذا، ستحتاج على الأرجح إلى إجراء مراجعة يدوية، وخاصة استهداف الاختلافات بين بايثون 2 و 3 المذكورة في القسم أعلاه. إن أردت استخدام future، فعليك إضافة عبارة الاستيراد التالية في جميع وحدات بايثون 2.7: from __future__ import print_function, division, absolute_imports, unicode_literals رغم أن هذا لن يعفيك من إعادة كتابة الشيفرة، إلا أنه سيضمن لك أن تتماشى شيفرة بايثون 2 مع صياغة بايثون 3. أخيرًا، يمكنك استخدام الحزمة pylint لتحديد ورصد أي مشكلات محتملة أخرى في الشيفرة. تحتوي هذه الحزمة على مئات القواعد التي تغطي مجموعة واسعة من المشكلات التي قد تطرأ، بما فيها قواعد الدليل PEP 8، بالإضافة إلى أخطاء الاستخدام. قد تجد أنّ بعض أجزاء شيفرتك تربك pylint وأدوات الترحيل التلقائي الأخرى. حاول تبسيطها، أو استخدم unittest. التكامل المستمر (Continuous Integration) إذا أردت أن تجعل شفرتك متوافقة مع عدة إصدارات من بايثون، فستحتاج إلى تشغيل الإطار unittest باستمرار وفق مبدأ التكامل المستمر (وليس يدويًا)، أي أن تفعل ذلك أكبر عدد ممكن من المرات أثناء عملية التطوير. إذا كنت تستخدم الحزمة six لصيانة التوافقية بين بايثون 2 و 3، فستحتاج إلى استخدام عدة بيئات عمل لأجل الاختبار. إحدى حزم إدارة البيئة التي قد تكون مفيدة لك هي الحزمة tox، إذ ستفحص تثبيتات الحزمة مع مختلف إصدارات بايثون، وإجراء الاختبارات في كل بيئة من بيئات عملك، كما يمكن أن تكون بمثابة واجهة عمل للتكامل المستمر. خلاصة من المهم أن تعلم أنه مع ازدياد تركيز المطورين على بايثون 3، فستصبح اللغة أكثر دقةً وتماشيًا مع احتياجات المبرمجين، وسيضعف دعم بايثون 2.7. إذا قرَّرت أن تجعل شيفرتك متوافقة مع كل من بايثون 2 و بايثون 3، فقد تواجه صعوبة في ذلك لأنَّ بايثون 2 ستتلقى دعمًا أقل مع مرور الوقت. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Port Python 2 Code to Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة السابقة: كيف تستخدم التسجيل Logging في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  2. التنقيح (debugging) هو جزء من عملية تطوير البرمجيات، ويروم البحث عن الأخطاء والمشاكل في الشيفرة، والتي تحول دون تنفيذ البرنامج تنفيذًا صحيحًا. الوحدة code هي إحدى الأدوات المفيدة التي يمكن استخدامها لمحاكاة المترجم (interpreter) التفاعلي، إذ توفر هذه الوحدة فرصةً لتجربة الشيفرة التي تكتبها. قبل أن تكمل هذا المقال، أنصحك بمطالعة المقال السابق عن منقح بايثون: كيف تستخدم منقح بايثون. فهم الوحدة code بدلاً من تفحص الشيفرة باستخدام منقح، يمكنك إضافة الوحدة code لوضع نقاط لإيقاف تنفيذ البرنامج، والدخول في الوضع التفاعلي لتفحص ومتابعة كيفية عمل الشيفرة. الوحدة code هي جزء من مكتبة بايثون القياسية. هذه الوحدة مفيدةٌ لأنَّها ستمكنك من استخدام مترجم دون التضحية بالتعقيد والاستدامة التي توفرها ملفات البرمجة. فيمكنك عبر استخدام الوحدة code تجنب استخدام الدالة print()‎‎‎ في شيفرتك لأجل التنقيح، لأنها طريقة غير عملية. لاستخدام هذه الوحدة في تنقيح الأخطاء، يمكنك استخدام الدالة interact()‎ الخاصة بالوحدة code، والتي توقف تنفيذ البرنامج عند استدعائها، وتوفر لك سطر أوامر تفاعلي حتى تتمكن من فحص الوضع الحالي لبرنامجك. تُكتَب هذه الدالة هكذا: code.interact(banner=None, readfunc=None, local=None, exitmsg=None) تُنفِّذ هذه الدالة حلقة اقرأ-قيِّم-اطبع (تختصر إلى REPL، أي Read–eval–print loop، وتنشئ نسخة من الصنف InteractiveConsole، والذي يحاكي سلوك مترجم بايثون التفاعلي. هذه هي المعاملات الاختيارية: banner: يمكن أن تعطيه سلسلة نصية لتعيين موضع إطلاق المترجم. readfunc: يمكن استخدامه مثل التابع InteractiveConsole.raw_input‎()‎. local: سيعينّ فضاء الأسماء (namespace) الافتراضي لحلقة المترجم (interpreter loop). exitmsg: يمكن إعطاؤه سلسلة نصية لتعيين موضع توقف المترجم. مثلًا، يمكن استخدام المعامل local بهذا الشكل: local=locals()‎ لفضاء أسماء محلي. local=globals()‎ لفضاء أسماء عام. local=dict(globals(), **locals())‎ لاستخدام كل من فضاء الأسماء العام، وفضاء الأسماء المحلي الحالي. المعامل exitmsg جديد، ولم يظهر حتى إصدار بايثون 3.6، لذلك إن كنت تستخدم إصدارًا أقدم، فحدّثه، أو لا تستخدم المعامل exitmsg. ضع الدالة interact()‎‎ حيث تريد إطلاق المترجم التفاعلي في الشيفرة. كيفية استخدام الوحدة code لتوضيح كيفية استخدام الوحدة code، سنكتب بُريمجًا عن الحسابات المصرفية يسمى balances.py. سنعيّن المعامل المحلي عند القيمة locals()‎‎‎ لجعل فضاء الأسماء محليًّا. # `code` استيراد الوحدة import code bal_a = 2324 bal_b = 0 bal_c = 409 bal_d = -2 account_balances = [bal_a, bal_b, bal_c, bal_d] def display_bal(): for balance in account_balances: if balance < 0: print("Account balance of {} is below 0; add funds now." .format(balance)) elif balance == 0: print("Account balance of {} is equal to 0; add funds soon." .format(balance)) else: print("Account balance of {} is above 0.".format(balance)) # لبدء المترجم بفضاء أسماء محلي interact()‎‎‎ استخدام code.interact(local=locals()) display_bal() لقد استدعينا الدالة code.interact()‎‎‎ مع المعامل local=locals()‎ لاستخدام فضاء الأسماء المحلي كقيمة افتراضية داخل حلقة المترجم. لنُنفِّذ البرنامج أعلاه باستخدام الأمر python3 إذا لم نكن تعمل في بيئة افتراضية، أو الأمر python خلاف ذلك: python balances.py بمجرد تنفيذ البرنامج، سنحصل على المخرجات التالية: Python 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> سيتم وضع المؤشر في نهاية السطر ‎>>>‎، كما لو أنك في سطر الأوامر التفاعلي. من هنا، يمكنك استدعاء الدالة print()‎ لطباعة المتغيرات والدوال وغير ذلك: >>> print(bal_c) 409 >>> print(account_balances) [2324, 0, 409, -2] >>> print(display_bal()) Account balance of 2324 is 0 or above. Account balance of 0 is equal to 0, add funds soon. Account balance of 409 is 0 or above. Account balance of -2 is below 0, add funds now. None >>> print(display_bal) <function display_bal at 0x104b80f28> >>> نرى أنه باستخدام فضاء الأسماء المحلي، يمكننا طباعة المتغيرات، واستدعاء الدالة. يُظهر الاستدعاء الأخير للدالة print()‎ أنّ الدالة display_bal موجودة في ذاكرة الحاسوب. بعد أن تنتهي من العمل على المترجم، يمكنك الضغط على CTRL + D في الأنظمة المستندة إلى *نكس، أو CTRL + Z في أنظمة ويندوز لمغادرة سطر الأوامر ومتابعة تنفيذ البرنامج. إذا أردت الخروج من سطر الأوامر دون تنفيذ الجزء المتبقي من البرنامج، فاكتب quit()‎‎‎، وسيتوقف البرنامج. في المثال التالي، سنستخدم المُعاملين banner و exitmsg: # لبدء المترجم interact()‎‎‎ استخدم الدالة code.interact(banner="Start", local=locals(), exitmsg="End") display_bal() عند تنفيذ البرنامج، ستحصل على المخرجات التالية: Start >>> يتيح لك استخدام المعامل banner تعيين عدة نقاط داخل شيفرتك، مع القدرة على تحديدها. على سبيل المثال، يمكن أن يكون لديك معامل banner يطبع السلسلة النصية "In for-loop" مع معامل exmsg يطبع "Out of for-loop"، وذلك حتى تعرف مكانك بالضبط في الشيفرة. من هنا، يمكننا استخدام المترجم كالمعتاد. بعد كتابة CTRL + D للخروج من المترجم، ستحصل على رسالة الخروج، وسيتم تنفيذ الدالة: End Account balance of 2324 is 0 or above. Account balance of 0 is equal to 0, add funds soon. Account balance of 409 is 0 or above. Account balance of -2 is below 0, add funds now. سيتم تنفيذ البرنامج بالكامل بعد الجلسة التفاعلية. بمجرد الانتهاء من استخدام الوحدة code لتنقيح الشيفرة، يجب عليك إزالة دوال الوحدة code وعبارة الاستيراد حتى يُنفَّذ البرنامج مثل المعتاد. خلاصة تُستخدَم الوحدة code لإطلاق سطر الأوامر التفاعلي لتفحُّص الشيفرة خطوةً بخطوة بقصد فهم سلوكها، وتعديل الشيفرة إن لزم الأمر. لقراءة المزيد حول هذا الموضوع، يمكنك مطالعة التوثيق الرسمي للوحدة code. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Debug Python with an Interactive Console لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيف تستخدم التسجيل Logging في بايثون 3 المقالة السابقة: كيف تستخدم منقح بايثون المرجع الشامل إلى تعلم لغة بايثون
  3. التعددية الشكلية (Polymorphism) هي القدرة على استخدام واجهة موحدة لعدة أشكال مختلفة، مثل أنواع البيانات أو الأصناف . هذا يسمح للدوال باستخدام كيانات من أنواع مختلفة. بالنسبة للبرامج الكائنية في بايثون، هذا يعني أنه يمكن استخدام كائن معين ينتمي إلى صنف مُعيَّن كما لو كان ينتمي إلى صنف مختلف. تسمح التعددية الشكلية بكتابة شيفرات مرنة ومجرّدَة وسهلة التوسيع والصيانة. سوف تتعلم في هذا الدرس كيفية تطبيق التعددية الشكلية على أصناف بايثون. ما هي التعددية الشكلية؟ التعددية الشكلية هي إحدى السمات الأساسية للأصناف في بايثون، وتُستخدَم عندما تكون هناك توابع لها نفس الأسماء في عدة أصناف، أو أصناف فرعية. يسمح ذلك للدوال باستخدام كائنات من أيٍّ من تلك الأصناف والعمل عليها دون الاكتراث لنوعها. يمكن تنفيذ التعددية الشكلية عبر [الوراثة](رابط المقالة السابقة)، أو باستخدام توابعِ الأصناف الفرعية، أو إعادة تعريفها (overriding). يستخدم بايثون نظام أنواع (typing) خاص، يسمى «نظام التحقق من الأنواع: البطة نموذجًا» (Duck Typing)، وهو حالة خاصة من أنظمة التحقق من الأنواع الديناميكية (Dynamic Typing). يستخدم هذا النظامُ التعدُّديةَ الشكلية، بما في ذلك الربط المتأخر، والإيفاد الديناميكي. يعتمد هذا النظام على «نموذج البطة» بناءً على اقتباسٍ للكاتب جيمس ويتكومب رايلي: خُصِّص هذا المفهوم من قبل مهندس الحاسوب الإيطالي أليكس مارتيلي (Alex Martelli) في رسالة إلى مجموعة comp.lang.python، يقوم نظام التحقق من الأنواع هذا الذي يعتمد البطة نموذجًا على تعريف الكائن من منظور ملاءمة الغرض الذي أُنشِئ لأجله. عند استخدام نظام أنواع عادي، فإنّ ملاءمة الكائن لغرض مُعيَّن يتحدد بنوع الكائن فقط، ولكن في نموذج البطة، يَتحدَّد ذلك بوجود التوابع والخاصيات الضرورية لذلك الغرض بدلًا من النوع الحقيقي للكائن. بمعنى آخر، إذا أردت أن تعرف إن كان الكائن بطةً أم لا، فعليك التحقق مما إذا كان ذلك الكائن يمشي مشي البطة، وصوته كصوت البطة، بدلًا من أن تسأل عما إذا كان الكائن بطةً. عندما تحتوي عدة أصناف أو أصناف فرعية على توابع لها نفس الأسماء، ولكن بسلوكيات مختلفة، نقول إنّ تلك الأصناف متعددة الأشكال (polymorphic) لأنها تستعمل واجهة موحدة يمكن استخدامها مع كيانات من أنواع مختلفة. يمكن للدوال تقييم ومعالجة هذه التوابع متعدِّدة الأشكال دون معرفة أصنافها. إنشاء أصناف متعددة الأشكال للاستفادة من التَعدُّدية الشكلية، سننشئ صنفين مختلفين لاستخدامهما مع كائنين مختلفين. يحتاج هذان الصنفان المختلفان واجهة موحدة يمكن استخدامها بطريقة تعدُّدية الشكل (polymorphically)، لذلك سنعرّف فيهما توابع مختلفة، ولكن لها نفس الاسم. سننشئ صنفًا باسم ‎Shark‎ وصنفُا آخر باسم ‎Clownfish‎، وسيُعرِّف كل منهما التوابع ‎swim()‎ و ‎swim_backwards()‎ و ‎skeleton()‎. class Shark(): def swim(self): print("القرش يسبح.") def swim_backwards(self): print("لا يمكن للقرش أن يسبح إلى الوراء، لكن يمكنه أن يغوص إلى الوراء.") def skeleton(self): print("هيكل القرش مصنوع من الغضروف.") class Clownfish(): def swim(self): print("سمكة المهرج تسبح.") def swim_backwards(self): print("يمكن لسمكة المهرج أن تسبح إلى الخلف.") def skeleton(self): print("هيكل سمكة المهرج مصنوع من العظام.") في الشيفرة أعلاه، لدى الصنفين ‎Shark‎ و ‎Clownfish‎ ثلاث توابع تحمل نفس الاسم بيْد أنّ وظائف تلك التوابع تختلف من صنف لآخر. دعنا نستنسخ (instantiate) من هذين الصنفين كائنين: ... sammy = Shark() sammy.skeleton() casey = Clownfish() casey.skeleton() عند تنفيذ البرنامج باستخدام الأمر ‎python polymorphic_fish.py‎، يمكننا أن نرى أنّ كل كائن يتصرف كما هو متوقع: هيكل القرش مصنوع من الغضروف. هيكل سمكة المهرج مصنوع من العظام. الآن وقد أصبح لدينا كائنان يستخدمان نفس الواجهة، فبمقدورنا استخدام هذين الكائنين بنفس الطريقة بغض النظر عن نوعيهما. التعددية الشكلية في توابع الأصناف لإظهار كيف يمكن لبايثون استخدام الصنفين المختلفين اللذين عرّفناهما أعلاه بنفس الطريقة، سننشئ أولاً حلقة for، والتي ستمر على صف من الكائنات. ثم سنستدعي التوابع بغض النظر عن نوع الصنف الذي ينتمي إليه كل كائن. إلا أننا سنفترض أنّ تلك التوابع موجودة في كل تلك الأصناف. ... sammy = Shark() casey = Clownfish() for fish in (sammy, casey): fish.swim() fish.swim_backwards() fish.skeleton() لدينا كائنان، ‎sammy‎ من الصنف ‎Shark‎، و ‎casey‎ من الصنف ‎Clownfish‎. تمر حلقة ‎for‎ على هذين الكائنين، وتستدعي التوابع ‎swim()‎ و ‎swim_backwards()‎ و ‎skeleton()‎ على كل منها. عند تنفيذ البرنامج، سنحصل على المخرجات التالية: القرش يسبح. لا يمكن للقرش أن يسبح إلى الوراء، لكن يمكنه أن يغوص إلى الوراء. هيكل القرش مصنوع من الغضروف. سمكة المهرج تسبح. يمكن لسمكة المهرج أن تسبح إلى الخلف. هيكل سمكة المهرج مصنوع من العظام. مرت الحلقة ‎for‎ على الكائن ‎sammy‎ من الصنف ‎Shark‎، ثم على الكائن ‎casey‎ المنتمي إلى الصنف ‎Clownfish‎، لذلك نرى التوابع الخاصة بالصنف ‎Shark‎ قبل التوابع الخاصة بالصنف ‎Clownfish‎. يدلُّ هذا على أنَّ بايثون تستخدم هذه التوابع دون أن تعرف أو تعبأ بتحديد نوع الصنف الخاص بالكائنات. وهذا مثال حي على استخدام التوابع بطريقة مُتعدِّدَة الأشكال. التعددية الشكلية في الدوال يمكننا أيضًا إنشاء دالة تقبل أيّ شيء، وهذا سيسمح باستخدام التعددية الشكلية. لننشئ دالة تسمى ‎in_the_pacific()‎، والتي تأخذ كائنًا يمكننا تسميته ‎fish‎. رغم أننا سنستخدم الاسم ‎fish‎، إلا أنه يمكننا استدعاء أي كائن في هذه الدالة: … def in_the_pacific(fish): بعد ذلك، سنجعل الدالة تستخدم الكائن ‎fish‎ الذي مرّرناه إليها. وفي هذه الحالة، سنستدعي التابع ‎swim()‎ المعرّف في كل من الصنفين ‎Shark‎ و ‎Clownfish‎: ... def in_the_pacific(fish): fish.swim() بعد ذلك، سننشئ نسخًا (instantiations) من الصنفين ‎Shark‎ و ‎Clownfish‎ لنمرّرهما بعد ذلك إلى نفس الدالة ‎in_the_pacific()‎: ... def in_the_pacific(fish): fish.swim() sammy = Shark() casey = Clownfish() in_the_pacific(sammy) in_the_pacific(casey) عند تنفيذ البرنامج، سنحصل على المخرجات التالية: القرش يسبح. سمكة المهرج تسبح. رغم أننا مرّرنا كائنًا عشوائيًا (‎fish‎) إلى الدالة ‎in_the_pacific()‎ عند تعريفها، إلا أننا ما زلنا قادرين على استخدامها استخدامًا فعالًا، وتمرير نسخ من الصنفين ‎Shark‎ و ‎Clownfish‎ إليها. استدعى الكائنُ ‎casey‎ التابعَ ‎swim()‎ المُعرَّف في الصنف ‎Clownfish‎، فيما استدعى الكائنُ ‎sammy‎ التابعَ ‎swim()‎ المُعرَّف في الصنف ‎Shark‎. خلاصة تسمح التعدُّدية الشكلية باستخدام الكائنات بغض النظر عن نوعها، وهذا يوفر لبايثون مرونة كبيرة، وقابلية لتوسيع الشيفرة الكائنية. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Apply Polymorphism to Classes in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيف تستخدم منقح بايثون المقالة السابقة: وراثة الأصناف في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  4. تُسِّهل البرمجة الكائنية كتابة شيفرات قابلة لإعادة الاستخدام وتجنب التكرار في مشاريع التطوير. إحدى الآليات التي تحقق بها البرمجة الكائنية هذا الهدف هي مفهوم الوراثة (inheritance)، التي بفضلها يمكن لصنفٍ فرعي (subclass) استخدام الشيفرة الخاصة بصنف أساسي (base class، ويطلق عليه «صنف أب» أيضًا) موجود مسبقًا. سيستعرض هذا الدرس بعض الجوانب الرئيسية لمفهوم الوراثة في بايثون، بما في ذلك كيفية إنشاء الأصناف الأساسية (parent classes) والأصناف الفرعية (child classes)، وكيفية إعادة تعريف (override) التوابع والخاصيات، وكيفية استخدام التابع ‎super()‎، وكيفية الاستفادة من الوراثة المتعددة (multiple inheritance). ما هي الوراثة؟ تقوم الوراثة على استخدام شيفرة صنف معين في صنف آخر أي يرث صنف يراد إنشاؤه شيفرة صنف آخر. يمكن تمثيل مفهوم الوراثة في البرمجة بالوراثة في علم الأحياء تمامًا، فالأبناء يرثون خاصيات معينة من آبائهم. ويمكن لطفل أن يرث طول والده أو لون عينيه بالإضافة إلى خاصيات أخرى جديدة خاصة فيه. كما يتشارك الأطفال نفس اسم العائلة الخاصة بآبائهم. ترث الأصناف الفرعية (subclasses، تُسمى أيضًا *الأصناف الأبناء [child classes]) التوابع والمتغيرات من *الأصناف الأساسية* (base classes، تسمى أيضًاالأصناف الآباء [parent classes]). مثلًا، قد يكون لدينا صنف أساسي يسمى ‎Parent‎ يحتوي متغيرات الأصناف ‎last_name‎ و ‎height‎ و ‎eye_color‎، والتي سيرثها الصنف الابن ‎Child‎. لمَّا كان الصنف الفرعي ‎Child‎ يرث الصنف الأساسي ‎Parent‎، فبإمكانه إعادة استخدام شيفرة ‎Parent‎، مما يسمح للمبرمج بكتابة شيفرة أوجز، وتقليل التكرار. الأصناف الأساسية تشكل الأصناف الأساسية أساسًا يمكن أن تستند إليه الأصناف الفرعية المُتفرِّعة منها، إذ تسمح الأصناف الأساسية بإنشاء أصناف فرعية عبر الوراثة دون الحاجة إلى كتابة نفس الشيفرة في كل مرة. يمكن تحويل أي صنف إلى صنف أساسي، إذ يمكن استخدامه لوحده، أو جعله قالبًا (نموذجًا). لنفترض أّنّ لدينا صنفًا أساسيًا باسم ‎Bank_account‎، وصنفين فرعيين مُشتقين منه باسم ‎Personal_account‎ و ‎Business_account‎. ستكون العديد من التوابع مشتركة بين الحسابات الشخصية (Personalaccount) والحسابات التجارية (Businessaccount)، مثل توابع سحب وإيداع الأموال، لذا يمكن أن تنتمي تلك التوابع إلى الصنف الأساسي ‎Bank_account‎. سيكون للصنف ‎Business_account‎ توابع خاصة به، مثل تابع مخصص لعملية جمع سجلات ونماذج الأعمال، بالإضافة إلى متغير ‎employee_identification_number‎ موروث من الصنف الأب. وبالمثل، قد يحتوي الصنف ‎Animal‎ على التابعين ‎eating()‎ و ‎sleeping()‎، وقد يتضمن الصنف الفرعي ‎Snake‎ تابعين إضافيين باسم ‎hissing()‎ و ‎slithering()‎ خاصين به. دعنا ننشئ صنفًا أساسيًا باسم ‎Fish‎ لاستخدامه لاحقًا أساسًا لأصناف فرعية تمثل أنواع الأسماك. سيكون لكل واحدة من تلك الأسماك أسماء أولى وأخيرة، بالإضافة إلى خصائص مميزة خاصة بها. سننشئ ملفًا جديدًا يسمى ‎fish.py‎ ونبدأ بالباني، والذي سنعرّف داخله متغيري الصنف ‎first_name‎ و ‎last_name‎ لكل كائنات الصنف ‎Fish‎، أو أصنافه الفرعية. class Fish: def __init__(self, first_name, last_name="Fish"): self.first_name = first_name self.last_name = last_name القيمة الافتراضية للمتغير ‎last_name‎ هي السلسلة النصية ‎"Fish"‎، لأننا نعلم أنّ معظم الأسماك سيكون هذا هو اسمها الأخير. لنُضف بعض التوابع الأخرى: class Fish: def __init__(self, first_name, last_name="Fish"): self.first_name = first_name self.last_name = last_name def swim(self): print("The fish is swimming.") def swim_backwards(self): print("The fish can swim backwards.") لقد أضفنا التابعين ‎swim()‎ و ‎swim_backwards()‎ إلى الصنف ‎Fish‎ حتى يتسنى لكل الأصناف الفرعية استخدام هذه التوابع. ما دام أنّ معظم الأسماك التي ننوي إنشاءها ستكون عظمية (أي أنّ لها هيكلا عظميًا) وليس غضروفية (أي أن لها هيكلًا غضروفيًا)، فيمكننا إضافة بعض الخاصيات الإضافية إلى التابع ‎__init__()‎: class Fish: def __init__(self, first_name, last_name="Fish", skeleton="bone", eyelids=False): self.first_name = first_name self.last_name = last_name self.skeleton = skeleton self.eyelids = eyelids def swim(self): print("The fish is swimming.") def swim_backwards(self): print("The fish can swim backwards.") لا يختلف بناء الأصناف الأساسية عن بناء أي صنف آخر، إلا أننا نصممها لتستفيد منها الأصناف الفرعية المُعرّفة لاحقًا. الأصناف الفرعية الأصناف الفرعية هي أصناف ترث كل شيء من الصنف الأساسي. هذا يعني أنّ الأصناف الفرعية قادرة على الاستفادة من توابع ومتغيرات الصنف الأساسي. على سبيل المثال، سيتمكن الصنف الفرعي ‎Goldfish‎ المشتق من الصنف ‎Fish‎ من استخدام التابع ‎swim()‎ المُعرّف في ‎Fish‎ دون الحاجة إلى التصريح عنه. يمكننا النظر إلى الأصناف الفرعية على أنها أقسام من الصنف الأساسي. فإذا كان لدينا صنف فرعي يسمى ‎Rhombus‎ (معيّن)، وصنف أساسي يسمى ‎Parallelogram‎ (متوازي الأضلاع)، يمكننا القول أنّ المعين (‎Rhombus‎) هو متوازي أضلاع (‎Parallelogram‎). يبدو السطر الأول من الصنف الفرعي مختلفًا قليلًا عن الأصناف غير الفرعية، إذ يجب عليك تمرير الصنف الأساسي إلى الصنف الفرعي كمعامل: class Trout(Fish): الصنف ‎Trout‎ هو صنف فرعي من ‎Fish‎. يدلنا على هذا الكلمةُ ‎Fish‎ المُدرجة بين قوسين. يمكننا إضافة توابع جديدة إلى الأصناف الفرعية، أو إعادة تعريف التوابع الخاصة بالصنف الأساسي، أو يمكننا ببساطة قبول التوابع الأساسية الافتراضية باستخدام الكلمة المفتاحية ‎pass‎، وهو ما سنفعله في المثال التالي: ... class Trout(Fish): pass يمكننا الآن إنشاء كائن من الصنف ‎Trout‎ دون الحاجة إلى تعريف أي توابع إضافية. ... class Trout(Fish): pass terry = Trout("Terry") print(terry.first_name + " " + terry.last_name) print(terry.skeleton) print(terry.eyelids) terry.swim() terry.swim_backwards() لقد أنشأنا كائنًا باسم ‎terry‎ من الصنف ‎Trout‎، والذي سيستخدم جميع توابع الصنف ‎Fish‎ وإن لم نعرّفها في الصنف الفرعي ‎Trout‎. يكفي أن نمرر القيمة ‎"Terry"‎ إلى المتغير ‎first_name‎، أما المتغيرات الأخرى فقد جرى تهيئتها سلفًا. عند تنفيذ البرنامج، سنحصل على المخرجات التالية: Terry Fish bone False The fish is swimming. The fish can swim backwards. لننشئ الآن صنفًا فرعيًا آخر يعرّف تابعًا خاصا به. سنسمي هذا الصنف ‎Clownfish‎. سيسمح التابع الخاص به بالتعايش مع شقائق النعمان البحري: ... class Clownfish(Fish): def live_with_anemone(self): print("The clownfish is coexisting with sea anemone.") دعنا ننشئ الآن كائنًا آخر من الصنف ‎Clownfish‎: ... casey = Clownfish("Casey") print(casey.first_name + " " + casey.last_name) casey.swim() casey.live_with_anemone() عند تنفيذ البرنامج، سنحصل على المخرجات التالية: Casey Fish The fish is swimming. The clownfish is coexisting with sea anemone. تُظهر المخرجات أنّ الكائن ‎casey‎ المستنسخ من الصنف ‎Clownfish‎ قادر على استخدام التابعين ‎__init__()‎ و ‎swim()‎ الخاصين بالصنف ‎Fish‎، إضافة إلى التابع ‎live_with_anemone()‎ الخاص بالصنف الفرعي. إذا حاولنا استخدام التابع ‎live_with_anemone()‎ في الكائن ‎Trout‎، فسوف يُطلق خطأ: terry.live_with_anemone() AttributeError: 'Trout' object has no attribute 'live_with_anemone' ذلك أنَّ التابع ‎live_with_anemone()‎ ينتمي إلى الصنف الفرعي ‎Clownfish‎ فقط، وليس إلى الصنف الأساسي ‎Fish‎. ترث الأصناف الفرعية توابع الصنف الأساسي الذي اشتُقَّت منه، لذا يمكن لكل الأصناف الفرعية استخدام تلك التوابع. إعادة تعريف توابع الصنف الأساسي في المثال السابق عرّفنا الصنف الفرعي ‎Trout‎ الذي استخدم الكلمة المفتاحية ‎pass‎ ليرث جميع سلوكيات الصنف الأساسي ‎Fish‎، وعرّفنا كذلك صنفًا آخر ‎Clownfish‎ يرث جميع سلوكيات الصنف الأساسي، ويُنشئ أيضًا تابعًا خاصًا به. قد نرغب في بعض الأحيان في استخدام بعض سلوكيات الصنف الأساسي، ولكن ليس كلها. يُطلَق على عملية تغيير توابع الصنف الأساسي «إعادة التعريف» (Overriding). عند إنشاء الأصناف الأساسية أو الفرعية، فلا بد أن تكون لك رؤية عامة لتصميم البرنامج حتى لا تعيد تعريف التوابع إلا عند الضرورة. سننشئ صنفًا فرعيًا ‎Shark‎ مشتقًا من الصنف الأساسي ‎Fish‎، الذي سيمثل الأسماك العظمية بشكل أساسي، لذا يتعين علينا إجراء تعديلات على الصنف ‎Shark‎ المخصص في الأصل للأسماك الغضروفية. من منظور تصميم البرامج، إذا كانت لدينا أكثر من سمكة غير عظمية واحدة، فيُستحب أن ننشئ صنفًا خاصًا بكل نوع من هذين النوعين من الأسماك. تمتلك أسماك القرش، على عكس الأسماك العظمية، هياكل مصنوعة من الغضاريف بدلاً من العظام. كما أنّ لديها جفونًا، ولا تستطيع السباحة إلى الوراء، كما أنها قادرة على المناورة للخلف عن طريق الغوص. على ضوء هذه المعلومات، سنعيد تعريف الباني ‎__init__()‎ والتابع ‎swim_backwards()‎. لا نحتاج إلى تعديل التابع ‎swim()‎ لأنّ أسماك القرش يمكنها السباحة. دعنا نلقي نظرة على هذا الصنف الفرعي: ... class Shark(Fish): def __init__(self, first_name, last_name="Shark", skeleton="cartilage", eyelids=True): self.first_name = first_name self.last_name = last_name self.skeleton = skeleton self.eyelids = eyelids def swim_backwards(self): print("The shark cannot swim backwards, but can sink backwards.") لقد أعدنا تعريف المعاملات التي تمت تهيئتها في التابع ‎__init__()‎، فأخذ المتغير ‎last_name‎ القيمة ‎"Shark"‎، كما أُسنِد إلى المتغير ‎skeleton‎ القيمة ‎"cartilage"‎، فيما أُسنِدَت القيمة المنطقية ‎True‎ إلى المتغير ‎eyelids‎. يمكن لجميع نُسخ الصنف إعادة تعريف هذه المعاملات. يطبع التابع ‎swim_backwards()‎ سلسلة نصية مختلفة عن تلك التي يطبعها في الصنف الأساسي ‎Fish‎، لأنّ أسماك القرش غير قادرة على السباحة للخلف كما تفعل الأسماك العظمية. يمكننا الآن إنشاء نسخة من الصنف الفرعي ‎Shark‎، والذي سيستخدم التابع ‎swim()‎ الخاص بالصنف الأساسي ‎Fish‎: ... sammy = Shark("Sammy") print(sammy.first_name + " " + sammy.last_name) sammy.swim() sammy.swim_backwards() print(sammy.eyelids) print(sammy.skeleton) عند تنفيذ هذه الشيفرة، سنحصل على المخرجات التالية: Sammy Shark The fish is swimming. The shark cannot swim backwards, but can sink backwards. True cartilage لقد أعاد الصنف الفرعي ‎Shark‎ تعريف التابعين ‎__init__()‎ و ‎swim_backwards()‎ الخاصين بالصنف الأساسي ‎Fish‎، وورث في نفس الوقت التابع ‎swim()‎ الخاص بالصنف الأساسي. الدالة ‎super()‎ يمكنك باستخدام الدالة ‎super()‎ الوصول إلى التوابع الموروثة التي أُعيدت كتابتها. عندما نستخدم الدالة ‎super()‎، فإننا نستدعي التابع الخاص بالصنف الأساسي لاستخدامه في الصنف الفرعي. على سبيل المثال، قد نرغب في إعادة تعريف جانب من التابع الأساسي وإضافة وظائف معينة إليه، ثم بعد ذلك نستدعي التابع الأساسي لإنهاء بقية العمل. في برنامج خاص بتقييم الطلاب مثلًا، قد نرغب في تعريف صنف فرعي ‎Weighted_grade‎ يرث الصنف الأساسي ‎Grade‎، ونعيد فيه تعريف التابع ‎calculate_grade()‎ الخاص بالصنف الأساسي من أجل تضمين شيفرة خاصة بحساب التقدير المرجّح (weighted grade)، مع الحفاظ على بقية وظائف الصنف الأساسي. عبر استدعاء التابع ‎super()‎، سنكون قادرين على تحقيق ذلك. عادة ما يُستخدم التابع ‎super()‎ ضمن التابع ‎__init__()‎، لأنّه المكان الذي ستحتاج فيه على الأرجح إلى إضافة بعض الوظائف الخاصة إلى الصنف الفرعي قبل إكمال التهيئة من الصنف الأساسي. لنضرب مثلًا لتوضيح ذلك، دعنا نعدّل الصنف الفرعي ‎Trout‎. نظرًا لأنّ سمك السلمون المرقَّط من أسماك المياه العذبة، فلنضف متغيرًا اسمه ‎water‎ إلى التابع ‎__init__()‎، ولنُعطه القيمة ‎"freshwater"‎، ولكن مع الحفاظ على باقي متغيرات ومعاملات الصنف الأساسي: ... class Trout(Fish): def __init__(self, water = "freshwater"): self.water = water super().__init__(self) ... لقد أعدنا تعريف التابع ‎__init__()‎ في الصنف الفرعي ‎Trout‎، وغيرنا سلوكه موازنةً بالتابع ‎__init__()‎ المُعرَّف سلفًا في الصنف الأساسي ‎Fish‎. لاحظ أننا استدعينا التابع ‎__init__()‎ الخاص بالصنف ‎Fish‎ بشكل صريح ضمن التابع ‎__init__()‎ الخاص بالصنف ‎Trout‎،. بعد إعادة تعريف التابع، لم نعد بحاجة إلى تمرير ‎first_name‎ كمعامل إلى ‎Trout‎، وفي حال فعلنا ذلك، فسيؤدي ذلك إلى إعادة تعيين ‎freshwater‎ بدلاً من ذلك. سنُهيِّئ بعد ذلك الخاصية ‎first_name‎ عن طريق استدعاء المتغير في الكائن خاصتنا. الآن يمكننا استدعاء متغيرات الصنف الأساسي التي تمت تهيئتها، وكذلك استخدام المتغير الخاص بالصنف الفرعي: ... terry = Trout() # تهيئة الاسم الأول terry.first_name = "Terry" # super() الخاص بالصنف الأساسي عبر __init__() استخدام print(terry.first_name + " " + terry.last_name) print(terry.eyelids) # المعاد تعريفها في الصنف الفرعي __init__() استخدام print(terry.water) # الخاص بالصنف الأساسي swim() استخدام التابع terry.swim() سنحصل على المخرجات التالية: Terry Fish False freshwater The fish is swimming. تُظهر المخرجات أنّ الكائن ‎terry‎ المنسوخ من الصنف الفرعي ‎Trout‎ قادر على استخدام المتغير ‎water‎ الخاص بتابع الصنف الفرعي ‎__init__()‎، إضافة إلى استدعاء المتغيرات ‎first_name‎ و ‎last_name‎ و ‎eyelids‎ الخاصة بالتابع ‎__init__()‎ المُعرَّف في الصنف الأساسي ‎Fish‎. يسمح لنا التابع ‎super()‎ المُضمن في بايثون باستخدام توابع الصنف الأساسي حتى بعد إعادة تعريف تلك التوابع في الأصناف الفرعية. الوراثة المُتعدِّدة (Multiple Inheritance) المقصود بالوراثة المتعددة هي قدرة الصنف على أن يرث الخاصيات والتوابع من أكثر من صنف أساسي واحد. هذا من شأنه تقليل التكرار في البرامج، ولكنه يمكن أيضًا أن يُعقِّد العمل، لذلك يجب استخدام هذا المفهوم بحذر. لإظهار كيفية عمل الوراثة المتعددة، دعنا ننشئ صنفًا فرعيًا ‎Coral_reef‎ يرث من الصنفين ‎Coral‎ و ‎Sea_anemone‎. يمكننا إنشاء تابع في كل صنف أساسي، ثم استخدام الكلمة المفتاحية ‎pass‎ في الصنف الفرعي ‎Coral_reef‎: class Coral: def community(self): print("Coral lives in a community.") class Anemone: def protect_clownfish(self): print("The anemone is protecting the clownfish.") class CoralReef(Coral, Anemone): pass يحتوي الصنف ‎Coral‎ على تابع يسمى ‎community()‎، والذي يطبع سطرًا واحدًا، بينما يحتوي الصنف ‎Anemone‎ على تابع يسمى ‎protect_clownfish()‎، والذي يطبع سطرًا آخر. سنُمرِّر الصنفين كلاهما بين قوسين في تعريف الصنف CoralReef، ما يعني أنه سيرث الصنفين معًا. دعنا الآن ننشئ كائنًا من الصنف CoralReef: ... great_barrier = CoralReef() great_barrier.community() great_barrier.protect_clownfish() الكائن ‎great_barrier‎ مُشتقٌ الصنف ‎CoralReef‎، ويمكنه استخدام التوابع من كلا الصنفين الأساسيين. عند تنفيذ البرنامج، سنحصل على المخرجات التالية: Coral lives in a community. The anemone is protecting the clownfish. تُظهِر المخرجات أنَّ التوابع من كلا الصنفين الأساسيين استُخدِما بفعالية في الصنف الفرعي. تسمح لنا الوراثة المُتعدِّدة بإعادة استخدام الشيفرات البرمجية المكتوبة في أكثر من صنف أساسي واحد. وإذا تم تعريف التابع نفسه في أكثر من صنف أساسي واحد، فإنّ الصنف الفرعي سيستخدم التابع الخاص بالصنف الأساسي الذي ظهر أولًا في قائمة الأصناف المُمرَّرة إليه عند تعريفه. رغم فوائدها الكثيرة وفعاليتها، إلا أنَّ عليك توخي الحذر في استخدام الوراثة المُتعدِّدة، حتى لا ينتهي بك الأمر بكتابة برامج مُعقَّدة وغير مفهومة للمبرمجين الآخرين. خلاصة تعلمنا في هذا الدرس كيفية إنشاء أصناف أساسية وفرعية، وكيفية إعادة تعريف توابع وخاصيات الأصناف الأساسية داخل الأصناف الفرعية باستخدام التابع ‎super()‎، إضافة إلى مفهوم الوراثة المتعددة. الوراثة هي إحدى أهم ميزات البرمجة الكائنية التي تجعلها متوافقة مع مبدأ DRY (لا تكرر نفسك)، وهذا يحسن إنتاجية المبرمجين، ويساعدهم على تصميم برامج فعالة وواضحة. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Class Inheritance in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية تطبيق التعددية الشكلية (Polymorphism) على الأصناف المقالة السابقة: فهم متغيرات الأصناف والنسخ في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  5. تسمح البرمجة الكائنية باستخدام متغيرات على مستوى الصنف، أو على مستوى النسخة (instance). المتغيرات هي رموز (symbols) تدل على قيمة تستخدمها في برنامجك. يشار إلى المتغيرات على مستوى الصنف باسم متغيرات الصنف (class variables)، في حين تسمى المتغيرات الموجودة على مستوى النسخة متغيرات النسخة (instance variables). إذا توقعت أن يكون المتغير متسقًا في جميع نسخ الصنف، أو عندما تود تهيئة المتغير، فالأفضل أن تُعرِّف ذلك المتغير على مستوى الصنف. أما إن كنت تعلم أن المتغير سيختلف من نسخة إلى أخرى، فالأفضل أن تعرفه على مستوى النسخة. يسعى أحد مبادئ تطوير البرمجيات هو مبدأ DRY (اختصارًا للعبارة don’t repeat yourself، والذي يعني لا تكرر نفسك) إلى الحد من التكرار في الشيفرة. أي تلتزم البرمجة الكائنية بمبدأ DRY على تقليل التكرار في الشيفرة. ستتعلم في هذا الدرس كيفية استخدام متغيرات الصنف والنسخة في البرمجة الكائنية في بايثون. متغيرات الصنف تٌعرَّف متغيرات الصنف داخل الصنف وخارج كل توابعه وعادةً ما توضع مباشرة أسفل ترويسة الصنف، وقبل الباني (constructor) والتوابع الأخرى. ولمَّا كانت مملوكة للصنف نفسه، فستُشارَك مع جميع نُسَخ ذلك الصنف. وبالتالي، سيكون لها نفس القيمة بغض النظر عن النسخة، إلا إن كنت ستستخدم متغير الصنف لتهيئة متغير معيَّن. متغير الصنف يبدو كما يلي: class Shark: animal_type = "fish" في الشيفرة أعلاه أحلنا القيمة "fish" إلى المتغير animal_type. يمكننا إنشاء نسخة من الصنف Shark (سنطلق عليها new_shark)، ونطبع المتغير باستخدام الصياغة النقطية (dot notation): class Shark: animal_type = "fish" new_shark = Shark() print(new_shark.animal_type) لننفذ البرنامج: python shark.py سيعيد البرنامج قيمة المتغير: fish دعنا نضيف مزيدًا من متغيرات الصنف، ونطبعها: class Shark: animal_type = "fish" location = "ocean" followers = 5 new_shark = Shark() print(new_shark.animal_type) print(new_shark.location) print(new_shark.followers) يمكن أن تتألف متغيرات الصنف من أي نوع من البيانات المتاحة في بايثون تمامًا مثل أي متغير آخر. استخدمنا في هذا البرنامج السلاسل النصية والأعداد الصحيحة. لننفذ البرنامج مرة أخرى باستخدام الأمر python shark.py ونرى المخرجات: fish ocean 5 يمكن للنسخة new_shark الوصول إلى جميع متغيرات الصنف وطباعتها عند تنفيذ البرنامج. تُنشَأ متغيرات الصنف عند إنشاء الصنف مباشرةً (وليس عند إنشاء نسخة منه) وتحتل موضعًا لها في الذاكرة ويمكن لأي كائن مُشتَق (نسخة) من الصنف نفسه أن يصل إليها ويقرأ قيمتها. متغيرات النسخة تختلف متغيرات النسخة عن متغيرات الصنف أن النسخة المشتقة من الصنف هي من تملكها وليس الصنف نفسه أي تكون على مستوى النسخة وسيُنشَأ متغير مستقل في الذاكرة عند إنشاء كل نسخة. هذا يعني أنّ متغيرات النسخة ستختلف من كائن إلى آخر. تُعرَّف متغيرات النسخة ضمن التوابع على خلاف متغيرات الصنف. في مثال الصنف Shark أدناه، عّرفنا متغيري النسخة name و age: class Shark: def __init__(self, name, age): self.name = name self.age = age عندما ننشئ كائنًا من الصنف Shark، سيتعيّن علينا تعريف هذه المتغيرات، عبر تمريرها كمعاملات ضمن الباني (constructor)، أو أي تابع آخر. class Shark: def __init__(self, name, age): self.name = name self.age = age new_shark = Shark("Sammy", 5) كما هو الحال مع متغيرات الأصناف، يمكننا بالمثل طباعة متغيرات النسخة: class Shark: def __init__(self, name, age): self.name = name self.age = age new_shark = Shark("Sammy", 5) print(new_shark.name) print(new_shark.age) عند تنفيذ البرنامج أعلاه باستخدام python shark.py، سنحصل على المخرجات التالية: Sammy 5 تتألف المخرجات التي حصلنا عليها من قيم المتغيرات التي هيّأناها لأجل الكائن new_shark. لننشئ كائنًا آخر من الصنف Shark يسمى stevie: class Shark: def __init__(self, name, age): self.name = name self.age = age new_shark = Shark("Sammy", 5) print(new_shark.name) print(new_shark.age) stevie = Shark("Stevie", 8) print(stevie.name) print(stevie.age) يمرِّر الكائن stevie المعاملات إلى الباني لتعيين قيم متغيرات النسخة الخاصة به. تسمح متغيرات النسخة، المملوكة لكائنات الصنف، لكل كائن أو نسخة أن تكون لها متغيرات خاصة بها ذات قيم مختلفة عن بعضها بعضًا. العمل مع متغيرات الصنف والنسخة معًا غالبًا ما تُستخدم متغيرات الصنف ومتغيرات النسخة في نفس الشيفرة، ويوضح المثال التالي يستخدم الصنف Shark الذي أنشأناه سابقًا هذا الأمر. تشرح التعليقات في البرنامج كل خطوة من خطوات العملية. class Shark: # متغيرات الصنف animal_type = "fish" location = "ocean" # name و age باني مع متغيري النسخة def __init__(self, name, age): self.name = name self.age = age # followers تابع مع متغير النسخة def set_followers(self, followers): print("This user has " + str(followers) + " followers") def main(): # الكائن الأول، إعداد متغيرات النسخة في الباني sammy = Shark("Sammy", 5) # name طباعة متغير النسخة print(sammy.name) # location طباعة متغير الصنف print(sammy.location) # الكائن الثاني stevie = Shark("Stevie", 8) # name طباعة متغير النسخة print(stevie.name) # followers لتمرير متغير النسخة set_followers استخدام التابع stevie.set_followers(77) # animal_type طباعة متغير الصنف print(stevie.animal_type) if __name__ == "__main__": main() عند تنفيذ البرنامج باستخدام python shark.py، سنحصل على المخرجات التالية: Sammy ocean Stevie This user has 77 followers fish خلاصة في البرمجة الكائنية، يشار إلى المتغيرات المُعرَّفة على مستوى الصنف بمتغيرات الصنف، في حين تسمى المتغيرات المُعرّفة على مستوى الكائن بمتغيرات النسخة. يتيح لنا هذا التمييز استخدام متغيرات ذات قيم واحدة بينها عبر متغيرات الصنف، أو استخدام متغيرات مختلفة لكل كائن على حدة عبر متغيرات النسخة. كما يضمن استخدام المتغيرات الخاصة بالصنف أو النسخة أن تكون الشيفرة متوافقة مع مبدأ DRY. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Class and Instance Variables in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضا المقالة التالية: وراثة الأصناف في بايثون 3 المقالة السابقة: كيفية إنشاء الأصناف وتعريف الكائنات في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  6. بايثون لغةٌ برمجة كائنية (object-oriented programming language). تركز البرمجة الكائنية (OOP) على كتابة شيفرات قابلة لإعادة الاستخدام، على عكس البرمجة الإجرائية (procedural programming) التي تركز على كتابة تعليمات صريحة ومتسلسلة. تتيح البرمجة الكائنية لمبرمجي بايثون كتابة شيفرات سهلة القراءة والصيانة، وهذا مفيد للغاية عند تطوير البرامج المُعقَّدة. التمييز بين الأصناف والكائنات أحدُ المفاهيم الأساسية في البرمجة الكائنية، ويوضح التعريفان التاليان الفرق بين المفهومين: الصنف - نموذج عام تُنسج على منواله كائنات يُنشِئها المبرمج. يُعرِّف الصنف مجموعةً من الخاصيات التي تميز أي كائن يُستنسَخ (instantiated) منه. الكائن - نسخةٌ (instance) من الصنف، فهو تجسيد عملي للصنف داخل البرنامج. تُستخدَم الأصناف لإنشاء أنماط، ثم تُستعمَل تلك الأنماط لإنشاء كائنات منها. ستتعلم في هذا الدرس كيفيَّة إنشاء الأصناف والكائنات، وتهيئة الخاصيات باستخدام تابعٍ بانٍ (constructor method)، والعمل على أكثر من كائن من نفس الصنف. الأصناف الأصناف هي نماذج عامة تُستخدم لإنشاء كائنات وسبق أن عرَّفناها آنفًا. تُنشَأ الأصناف باستخدام الكلمة المفتاحية ‎class‎، بشكل مشابه [لتعريف الدوال](رابط المقالة 34) الذي يكون باستخدام الكلمة المفتاحية ‎def‎. دعنا نعرّف صنفًا يسمى ‎Shark‎، ونجعل له تابعين مرتبطين به، swim و be_awesome: class Shark: def swim(self): print("The shark is swimming.") def be_awesome(self): print("The shark is being awesome.") تُسمَّى مثل هذه الدوال «توابعًا» (methods) لأنهما معرفتان داخل الصنف ‎Shark‎؛ أي أنهما دالتان تابعتان للصنف ‎Shark‎. الوسيط الأول لهاتَين الدالتين هو ‎self‎، وهو مرجع إلى الكائنات التي يتم بناؤها من هذا الصنف. للإشارة إلى نُسخ (أو كائنات) من الصنف، يوضع ‎self‎ دائمًا في البداية، لكن يمكن أن تكون معه وسائط أخرى. لا يؤدي تعريف الصنف ‎Shark‎ إلى إنشاء كائنات منه، وإنما يعرّف فقط النمط العام لتلك الكائنات، والتي يمكننا تعريفها لاحقًا. لذا، إذا نفّذت البرنامج أعلاه الآن، فلن يُعاد أي شيء. الكائنات الكائن هو نسخةٌ (instance) من صنف. ويمكن أن نأخذ الصنف ‎Shark‎ المُعرَّف أعلاه، ونستخدمه لإنشاء كائن يعدُّ نسخةً منه. سننشئ كائنًا ‎Shark‎ يسمى ‎sammy‎: sammy = Shark() لقد أحلنا على الكائن ‎sammy‎ ناتج الباني ‎Shark()‎، والذي يعيد نسخةً من الصنف. سنستخدم في الشيفرة التالية التابعين الخاصين بالكائن ‎sammy‎: sammy = Shark() sammy.swim() sammy.be_awesome() يستخدم الكائن ‎sammy‎ التابعين ‎swim()‎ و ‎be_awesome()‎، وقد استدعينَاهما باستعمال المعامل النقطي (‎.‎)، والذي يُستخدم للإشارة إلى خاصيات أو توابع الكائنات. في هذه الحالة، استدعينا تابعًا، لذلك استعملنا قوسين مثلما نفعل عند استدعاء دالة. الكلمة ‎self‎ هي معامل يُمرّر إلى توابع الصنف ‎Shark‎، في المثال أعلاه، يمثّل ‎self‎ الكائن ‎sammy‎. يتيح المعامل ‎self‎ للتوابع الوصول إلى خاصيات الكائن الذي استُدعيت معه. لاحظ أننا لم نمرر شيئًا داخل القوسين عند استدعاء التابع أعلاه، ذلك أنّ الكائن ‎sammy‎ يُمرّر تلقائيًا مع العامل النقطي. البرنامج التالي يوضح لنا الأمر: class Shark: def swim(self): print("The shark is swimming.") def be_awesome(self): print("The shark is being awesome.") def main(): sammy = Shark() sammy.swim() sammy.be_awesome() if __name__ == "__main__": main() لننفذ البرنامج لنرى ما سيحدث: python shark.py ستُطبع المخرجات التالية: The shark is swimming. The shark is being awesome. في الشيفرة أعلاه، استدعى الكائن ‎sammy‎ التابعين ‎swim()‎ و ‎be_awesome()‎ في الدالة الرئيسية ‎main()‎. الباني يٌستخدم الباني (Constructor Method) لتهيئة البيانات الأولية، ويُنفَّذ لحظة إنشاء الكائن. في تعريف الصنف، يأخذ الباني الاسم ‎__init__‎، وهو أول تابع يُعرّف في الصنف، ويبدو كما يلي: class Shark: def __init__(self): print("This is the constructor method.") إذا أضفت التابع ‎__init__‎ إلى الصنف ‎Shark‎ في البرنامج أعلاه، فسيَطبع البرنامجُ المخرجات التالية: This is the constructor method. The shark is swimming. The shark is being awesome. يُنفَّذ الباني تلقائيًا، لذا يستخدمه مطورو بايثون لتهيئة أصنافهم. سنُعدِّل الباني أعلاه، ونجعله يستخدم متغيرًا اسمه ‎name‎ سيمثّل اسم الكائن. في الشيفرة التالية، سيكون المتغير ‎name‎ المعامل المُمرَّر إلى الباني، ونحيل قيمته إلى الخاصية ‎self.name‎: class Shark: def __init__(self, name): self.name = name بعد ذلك، يمكننا تعديل السلاسل النصية في دوالنا للإشارة إلى اسم الصنف، على النحو التالي: class Shark: def __init__(self, name): self.name = name def swim(self): # الإشارة إلى الاسم print(self.name + " is swimming.") def be_awesome(self): # الإشارة إلى الاسم print(self.name + " is being awesome.") أخيرًا، يمكننا تعيين اسم الكائن ‎sammy‎ عند القيمة ‎"Sammy"‎ (أي قيمة الخاصية name) بتمريره إلى ‎Shark()‎ عند إنشائه: class Shark: def __init__(self, name): self.name = name def swim(self): print(self.name + " is swimming.") def be_awesome(self): print(self.name + " is being awesome.") def main(): # Shark تعيين اسم كائن sammy = Shark("Sammy") sammy.swim() sammy.be_awesome() if __name__ == "__main__": main() عرّفنا التابع ‎__init__‎، والذي يقبل مُعاملين ‎self‎ و name (تذكر أن المعامل ‎self‎ يُمرر تلقائيا إلى التابع)، ثم عرّفنا متغيرًا فيه. عند تنفيذ البرنامج: python shark.py سنحصل على: Sammy is swimming. Sammy is being awesome. لقد طُبع الاسم الذي مرّرناه إلى الكائن. ونظرًا لأنّ الباني يُنفّذ تلقائيًا، فلست بحاجة إلى استدعائه بشكل صريح، فيكفي تمرير الوسائط بين القوسين التاليين لاسم الصنف عند إنشاء نسخة جديدة منه. إذا أردت إضافة معامل آخر، مثل ‎age‎، فيمكن ذلك عبر تمريره إلى التابع ‎__init__‎: class Shark: def __init__(self, name, age): self.name = name self.age = age عند إنشاء الكائن ‎sammy‎، سنمرر عُمره أيضًا بالإضافة إلى اسمه: sammy = Shark("Sammy", 5) إذًا، تتيح البانيات تهيئة خاصيات الكائن لحظة إنشائه. العمل مع عدة كائنات تتيح لنا الأصناف إنشاء العديد من الكائنات المتماثلة التي تتبع نفس النمط. لتفهم ذلك بشكل أفضل، دعنا نضيف كائنًا آخر من الصنف ‎Shark‎ إلى برنامجنا: class Shark: def __init__(self, name): self.name = name def swim(self): print(self.name + " is swimming.") def be_awesome(self): print(self.name + " is being awesome.") def main(): sammy = Shark("Sammy") sammy.be_awesome() stevie = Shark("Stevie") stevie.swim() if __name__ == "__main__": main() لقد أنشأنا كائنًا ثانيًا من الصنف ‎Shark‎ يسمى ‎stevie‎، ومرّرنا إليه الاسم ‎"Stevie"‎. في هذا المثال، استدعينا التابع ‎be_awesome()‎ مع الكائن ‎sammy‎، والتابع ‎swim()‎ مع الكائن ‎stevie‎. لننفذ البرنامج: python shark.py سنحصل على المخرجات التالية: Sammy is being awesome. Stevie is swimming. يبدو ظاهرًا في المخرجات أننا نستخدم كائنين مختلفين، الكائن ‎sammy‎ والكائن ‎stevie‎، وكلاهما من الصنف ‎Shark‎. تتيح لنا الأصناف إنشاء عدة كائنات تتبع كلها نفس النمط دون الحاجة إلى بناء كل واحد منها من البداية. خلاصة تطرَّقنا في هذا الدرس إلى عِدَّة مفاهيم، مثل إنشاء الأصناف، وإنشاء الكائنات، وتهيئة الخاصيات باستخدام البانيات، والعمل مع أكثر من كائن من نفس الصنف. تُعدُّ البرمجة الكائنية أحد المفاهيم الضرورية التي ينبغي أن يتعلمها كل مبرمجي بايثون، لأنها تساعد على كتابة شيفرات قابلة لإعادة الاستخدام، إذ أنَّ الكائنات التي تُنشَأ في برنامج ما يمكن استخدامها في برامج أخرى. كما أنّ البرامج الكائنية عادة ما تكون أوضح وأكثر مقروئية، خصوصًا في البرامج المعقدة التي تتطلب تخطيطًا دقيقًا، وهذا بدوره يسهل صيانة البرامج مستقبلًا. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Construct Classes and Define Objects in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: فهم متغيرات الأصناف والنسخ في بايثون 3 المقالة السابقة: كيفية استخدام ‎args و ‎*kwargs في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  7. تعدُّ المعاملات في تعاريف الدوال كيانات مسماة تُحدِّد وسيطًا (argument) يمكن أن يُمرَّر إلى الدالة المُعرَّفة. أثناء البرمجة، قد لا تدرك جميع حالات الاستخدام الممكنة للشيفرة، لذا قد ترغب في توسيع خيارات المبرمجين المستقبليين الذين سيستخدمون الوحدة التي طورتها. سنتعلم في هذا الدرس كيفيَّة تمرير عدد متغير من الوسائط إلى دالة ما باستخدام الصياغتين ‎*args‎ و ‎**kwargs‎. الوسيط ‎*args في بايثون، يمكن استخدام الشكل أحادي النجمة ‎*args‎ كمعامل لتمرير قائمة غير محددة الطول من الوسائط غير المسماة (non-keyworded argument) إلى الدوال. تجدر الإشارة إلى أنّ النجمة (‎*‎) عنصر ضروري هنا، إذ رغم أنّ الكلمة ‎args‎ متعارف عليها بين المبرمجين، إلا أنها غير رسمية. لنلقِ نظرة على مثال لدالة تستخدم وسيطين: def multiply(x, y): print (x * y) عرّفنا في الشفرة أعلاه دالة تقبل وسيطين ‎x‎ و ‎y‎، عندما نستدعي هذه الدالة، سنحتاج إلى تمرير عددين موافقين للوسيطين ‎x‎ و ‎y‎. في هذا المثال، سنمرّر العدد الصحيح ‎5‎ إلى ‎x‎، والعدد الصحيح ‎4‎ إلى ‎y‎: def multiply(x, y): print (x * y) multiply(5, 4) عند تنفيذ الشفرة أعلاه: python lets_multiply.py سنحصل على المخرجات التالية: 20 ماذا لو قررنا لاحقًا أننا نود حساب ناتج ضرب ثلاثة أعداد بدلًا من عددين فقط؟ إذا حاولت تمرير عدد إضافي إلى الدالة، كما هو موضح أدناه: def multiply(x, y): print (x * y) multiply(5, 4, 3) فسيُطلَق الخطأ التالي: TypeError: multiply() takes 2 positional arguments but 3 were given إذا شككت أنك ستحتاج إلى استخدام المزيد من الوسائط لاحقًا، فالحل هو استخدام ‎*args‎ كمعامل. سنزيل المُعاملين ‎x‎ و ‎y‎ من الشفرة في المثال الأول، ونضع مكانهما ‎*args‎: def multiply(*args): z = 1 for num in args: z *= num print(z) multiply(4, 5) multiply(10, 9) multiply(2, 3, 4) multiply(3, 5, 10, 6) عندما ننفّذ هذه الشفرة، سنحصل على ناتج استدعاءات الدالة أعلاه: 20 90 24 900 يمكننا باستخدام الوسيط ‎*args‎ تمرير أي عدد نحب من الوسائط عند استدعاء الدالة بالإضافة إلى كتابة شيفرة أكثر مرونة، وإنشاء دوال تقبل عددًا غير محدد مسبقًا من الوسائط غير المسماة. الوسيط ‎**kwargs يُستخدَم الشكل ذو النجمتين ‎**kwargs‎ لتمرير قاموس متغير الطول من الوسائط المسماة إلى الدالة المعرّفة. مرة أخرى، النجمتان (‎**‎) ضروريتان، فمع أنّ استخدام الكلمة ‎kwargs‎ متعارف عليه لدى المبرمجين، إلا أنها غير رسمية. يمكن أن تأخذ ‎**kwargs‎، كما هو شأن ‎*args‎، أيّ عدد من الوسائط التي ترغب في تمريرها إلى الدالة بيْد أنّ ‎**kwargs‎ تختلف عن ‎*args‎ في أنها تستوجب تعيين أسماء المعاملات (keywords). في المثال التالي، ستطبع الدالة الوسيط ‎**kwargs‎ الممرر إليها. def print_kwargs(**kwargs): print(kwargs) سنستدعي الآن الدالة ونمرر إليها بعض الوسائط المسماة: def print_kwargs(**kwargs): print(kwargs) print_kwargs(kwargs_1="Shark", kwargs_2=4.5, kwargs_3=True) لننفذ البرنامج أعلاه: python print_kwargs.py سنحصل على المخرجات التالية: {'kwargs_3': True, 'kwargs_2': 4.5, 'kwargs_1': 'Shark'} اعتمادًا على إصدار بايثون 3 الذي تستخدمه، فقد لا يكون القاموس مرتبًا. في بايثون 3.6 وما بعده، ستحصل على أزواج قيمة-مفتاح (key-value) مرتبة، ولكن في الإصدارات السابقة، سيكون ترتيب الأزواج عشوائيًا. سيُنشَأ قاموس يسمى ‎kwargs‎، والذي يمكننا التعامل معه مثل أي قاموس عادي داخل الدالة. لننشئ برنامجًا آخر لإظهار كيفية استخدام ‎**kwargs‎. سننشئ دالة تطبع قاموسًا من الأسماء. أولاً، سنبدأ بقاموس يحتوي اسمين: def print_values(**kwargs): for key, value in kwargs.items(): print("The value of {} is {}".format(key, value)) print_values(my_name="Sammy", your_name="Casey") بعد تنفيذ البرنامج: python print_values.py سنحصل على مايلي: The value of your_name is Casey The value of my_name is Sammy قد لا تكون القواميس مرتَّبة، لذلك قد يظهر الاسم ‎Casey‎ أولاً، وقد يظهر ثانيًا. سنمرر الآن وسائط إضافية إلى الدالة لنرى كيف يمكن أن تقبل ‎**kwargs‎ أيّ عدد من الوسائط: def print_values(**kwargs): for key, value in kwargs.items(): print("The value of {} is {}".format(key, value)) print_values( name_1="Alex", name_2="Gray", name_3="Harper", name_4="Phoenix", name_5="Remy", name_6="Val" ) إذا نفّذنا البرنامج الآن، فسنحصل على المخرجات التالية، والتي قد تكون غير مرتبة: The value of name_2 is Gray The value of name_6 is Val The value of name_4 is Phoenix The value of name_5 is Remy The value of name_3 is Harper The value of name_1 is Alex يتيح لك استخدام ‎**kwargs‎ مرونةً كبيرةً في استخدام الوسائط المسماة. فعند استخدامها، لن نحتاج إلى معرفة مسبقة بعدد الوسائط التي ستمرر إلى الدالة. ترتيب الوسائط عند الخلط بين عدة أنواع من الوسائط داخل دالة، أو داخل استدعاء دالة، يجب أن تظهر الوسائط وفق الترتيب التالي: الوسائط العادية ‎*args‎ الوسائط المسمّاة ‎**kwargs‎ عمليًا، عند الجمع بين المعاملات العادية، والوسيطين ‎*args‎ و ‎**kwargs‎، فينبغي أن تكون وفق الترتيب التالي: def example(arg_1, arg_2, *args, **kwargs): ... وعند الجمع بين المعاملات العادية والمعاملات المسماة و ‎*args‎ و ‎**kwargs‎، ينبغي أن تكون وفق الترتيب التالي: def example2(arg_1, arg_2, *args, kw_1="shark", kw_2="blobfish", **kwargs): ... من المهم أن تأخذ في الحسبان ترتيب الوسائط عند إنشاء الدوال حتى لا تتسبب في إطلاق خطأٍ متعلقٍ بالصياغة. استخدام ‎*args و ‎**kwargs في استدعاءات الدوال يمكننا أيضًا استخدام ‎*args‎ و ‎**kwargs‎ لتمرير الوسائط إلى الدوال. أولاً، دعنا ننظر إلى مثال يستخدم ‎*args‎: def some_args(arg_1, arg_2, arg_3): print("arg_1:", arg_1) print("arg_2:", arg_2) print("arg_3:", arg_3) args = ("Sammy", "Casey", "Alex") some_args(*args) في الدالة أعلاه، هناك ثلاثة معاملات، وهي ‎arg_1‎ و ‎arg_‎ و ‎arg_3‎. ستطبع الدالة كل هذه الوسائط. بعد ذلك أنشأنا متغيرًا، وأَحلنا عليه عنصرًا تكراريًا (في هذه الحالة، صف)، ثم مرَّرنا ذلك المتغير إلى الدالة باستخدام الصياغة النجمية (asterisk syntax). عندما ننفّذ البرنامج باستخدام الأمر ‎python some_args.py‎، سنحصل على المخرجات التالية: arg_1: Sammy arg_2: Casey arg_3: Alex يمكننا أيضًا تعديل البرنامج أعلاه، واستخدام قائمة. سندمج أيضًا ‎*args‎ مع وسيط مسمى: def some_args(arg_1, arg_2, arg_3): print("arg_1:", arg_1) print("arg_2:", arg_2) print("arg_3:", arg_3) my_list = [2, 3] some_args(1, *my_list) إذا نفذنا البرنامج أعلاه، فسنحصل على المخرجات التالية: arg_1: 1 arg_2: 2 arg_3: 3 وبالمثل، يمكن استخدام الوسائط المسماة ‎**kwargs‎ لاستدعاء دالة. سننشئ متغيرًا، ونسند إليه قاموسًا من 3 أزواج مفتاح-قيمة (سنستخدم ‎kwargs‎ هنا، ولكن يمكنك تسميته ما تشاء)، ثم نُمرِّره إلى دالة ذات 3 وسائط: def some_kwargs(kwarg_1, kwarg_2, kwarg_3): print("kwarg_1:", kwarg_1) print("kwarg_2:", kwarg_2) print("kwarg_3:", kwarg_3) kwargs = {"kwarg_1": "Val", "kwarg_2": "Harper", "kwarg_3": "Remy"} some_kwargs(**kwargs) عند تنفيذ البرنامج أعلاه باستخدام الأمر ‎python some_kwargs.py‎، سنحصل على المخرجات التالية: kwarg_1: Val kwarg_2: Harper kwarg_3: Remy خلاصة يمكنك استخدام الصياغتين الخاصتين ‎*args‎ و ‎**kwargs‎ ضمن تَعاريف الدوال لتمرير أيّ عدد تشاء من الوسائط إليها. يُستحسن استخدام ‎*args‎ و ‎**kwargs‎ في المواقف التي تتوقع أن يظل فيها عدد المدخلات في قائمة الوسائط صغيرًا نسبيًا. وضع في ذهنك أن استخدام ‎*args‎ و ‎**kwargs‎ يحسِّن مقروئية الشيفرة، ويسهِّل على المبرمجين، ولكن ينبغي استخدامهما بحذر. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Use *args and **kwargs in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية إنشاء الأصناف وتعريف الكائنات في بايثون 3 المقالة السابقة: كيفية تعريف الدوال في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  8. الدالة (funtion) هي كتلة من التعليمات التي تنفِّذ إجراءً ما، ويمكن، بعد تعريفها، إعادة استخدامها في أكثر من موضع. تجعل الدوال الشيفرة تركيبية (modular)، مما يسمح باستخدام نفس الشفرة مرارًا وتكرارًا. تضم بايثون عددًا من الدوال المُضمّنة الشائعة، مثل: ‎print()‎: تطبع كائنًا في الطرفية. ‎int()‎: تحوّل أنواع البيانات النصية أو العددية إلى أعداد صحيحة. ‎len()‎: تعيد طول كائن، وغيرها من الدوال. تتضمن أسماء الدوال الأقواس، وقد تتضمن معاملات أيضًا. في هذا الدرس، سنتعلم كيفية تعريف الدوال، وكيفية استخدامها في البرامج. تعريف الدالة لنبدأ بتحويل البرنامج "مرحبًا بالعالم!" إلى دالة. أنشئ ملفًا نصيًا جديدًا، وافتحه في محرر النصوص المفضل عندك، ثم استدع البرنامج ‎hello.py‎. تُعرَّف الدالة باستخدام الكلمة المفتاحية ‎def‎، متبوعة باسم من اختيارك، متبوعًا بقوسين يمكن أن يَحتويا المعاملات التي ستأخذها الدالة، ثم ينتهي التعريف بنقطتين. في هذه الحالة، سنعرّف دالة باسم ‎hello()‎: def hello(): في الشفرة أعلاه، أعددنا السطر الأول من تعريف الدالة. بعد هذا، سنضيف سطرًا ثانيًا مُزاحًا بأربع مسافات بيضاء، وفيه سنكتب التعليمات التي ستنفّذها الدالة. في هذه الحالة، سنطبع العبارة مرحبا بالعالم في سطر الأوامر: def hello(): print("مرحبا بالعالم") لقد أتممنا تعريف دالتنا، غير أننا إن نَفَّذنا البرنامج الآن، فلن يحدث أيّ شيء، لأننا لم نستدع الدالة؛ لذلك، سنستدع الدالة عبر التعبير ‎hello()‎ خارج كتلة تعريف الدالة: def hello(): print("مرحبا بالعالم") hello() الآن، لننفّذ البرنامج: python hello.py يجب أن تحصل على المخرجات التالية: مرحبا بالعالم! بعض الدوال أكثر تعقيدًا بكثير من الدالة ‎hello()‎ التي عرّفناها أعلاه. على سبيل المثال، يمكننا استخدام for والتعليمات الشرطية، وغيرها داخل كتلة الدالة. على سبيل المثال، تستخدم الدالة المُعرّفة أدناه تعليمة شرطية للتحقق مما إذا كانت المدخلات الممرّرة إلى المتغير ‎name‎ تحتوي على حرف علة (vowel)، ثم تستخدم الحلقة ‎for‎ للمرور (iterate) على الحروف الموجودة في السلسلة النصية ‎name‎. # names() تعريف الدالة def names(): # وإحالة المدخلات عليه name إعداد المتغير name = str(input('أدخل اسمك:')) # يحتوي حرف علة name التحقق من أن if set('aeiou').intersection(name.lower()): print('اسمك يحوي حرف علة') else: print('اسمك لا يحوي حرف علة') # name المرور على حروف for letter in name: print(letter) # استدعاء الدالة names() تستخدم الدالة ‎names()‎ التي عرّفناها أعلاه تعليمة شرطية، وحلقة for، وهذا توضيح لكيفية تنظيم الشفرة البرمجية ضمن تعريف الدالة. يمكننا أيضًا جعل التعليمة الشرطية والحلقة ‎for‎ دالتين منفصلتين. إنّ تعريف الدوال داخل البرامج يجعل الشفرة البرمجية تركيبية (modular)، وقابلة لإعادة الاستخدام، وذلك سيتيح لنا استدعاء نفس الدالة دون إعادة كتابة شيفرتها كل مرة. المعاملات حتى الآن، عرّفنا دالة ذات قوسين فارغين لا تأخذ أيّ وسائط (arguments)، سنتعلم في هذا القسم كيفية تعريف المعاملات (parameters) وتمرير البيانات إلى الدوال. المعامل (parameter) هو كيان مُسمًّى يوضع في تعريف الدالة، ويعرّف وسيطًا (arguments) يمكن أن تقبله الدالة عند استدعائها. دعنا ننشئ برنامجًا صغيرًا يأخذ 3 معاملات ‎x‎ و ‎y‎ و ‎z‎. سننشئ دالة تجمع تلك المعاملات وفق عدة مجموعات ثم تطبع تلك حاصل جمعها. def add_numbers(x, y, z): a = x + y b = x + z c = y + z print(a, b, c) add_numbers(1, 2, 3) مرّرنا العدد ‎1‎ إلى المعامل ‎x‎، و ‎2‎ إلى المعامل ‎y‎، و ‎3‎ إلى المعامل ‎z‎. تتوافق هذه القيم مع المعاملات المقابلة لها في ترتيب الظهور. يُجرِي البرنامج العمليات الحسابية على المعاملات على النحو التالي: a = 1 + 2 b = 1 + 3 c = 2 + 3 تطبع الدالة أيضًا ‎a‎ و ‎b‎ و ‎c‎، وبناءً على العمليات الحسابية أعلاه، فإنّ قيمة ‎a‎ ستساوي العدد ‎3‎، و ‎b‎ ستساوي ‎4‎، و ‎c‎ ستساوي العدد ‎5‎. لننفّذ البرنامج: python add_numbers.py سنحصل على المخرجات التالية: 3 4 5 المعاملات هي وسائط يتم تعريفها عادة كمتغيرات ضمن تعريف الدالة. يمكن تعيين قيم إليها عند تنفيذ التابع بتمرير وسائط إلى الدالة. الوسائط المسمّاة تُستدعى المعاملات بحسب ترتيب ظهورها في تعريف الدالة، أما الوسائط المسماة (Keyword Arguments) فتُستخدَم بأسمائها في استدعاء الدالة. عند استخدام الوسائط المسمّاة، يمكنك استخدام المعاملات بأيّ ترتيب تريد، لأنّ مترجم بايثون سيستخدم الكلمات المفتاحية لمطابقة القيم مع المعاملات. سننشئ دالة تعرض معلومات الملف الشخصي للمستخدم، ونمرر إليها المُعامِلين ‎username‎ (سلسلة نصية)، و ‎followers‎ (عدد صحيح). # تعريف دالة ذات معاملات def profile_info(username, followers): print("Username: " + username) print("Followers: " + str(followers)) داخل تعريف الدالة، وضعنا ‎username‎ و ‎followers‎ بين قوسي الدالة ‎profile_info()‎ أثناء تعريفها. تطبع شفرة الدالة المعلومات الخاصة بالمستخدم على هيئة سلسلة نصية باستخدام المعاملين المُمرّرين. الآن، يمكننا استدعاء الدالة وتعيين المعاملات: def profile_info(username, followers): print("Username: " + username) print("Followers: " + str(followers)) # استدعاء الدالة مع تعيين المعاملات profile_info("sammyshark", 945) # استدعاء الدالة مع تمرير الوسائط المسماة إليها profile_info(username="AlexAnglerfish", followers=342) في الاستدعاء الأول للدالة، مرّرنا اسم المستخدم ‎sammyshark‎، وعدد المتابعين ‎945‎ بالترتيب الوارد في تعريف الدالة. أمّا في الاستدعاء الثاني للدالة، فقد استخدمنا الوسائط المسمّاة، وقمنا بتعيين قيم للوسائط ويمكن عكس الترتيب إن شئنا. لننفذ البرنامج: python profile.py سنحصل على المخرجات التالية: Username: sammyshark Followers: 945 Username: AlexAnglerfish Followers: 342 سنحصل في المخرجات على أسماء المستخدمين، وأعداد المتابعين لكلا المستخدمين. يمكننا تغيير ترتيب المعاملات، كما في المثال التالي: def profile_info(username, followers): print("Username: " + username) print("Followers: " + str(followers)) # تغيير ترتيب المعاملات profile_info(followers=820, username="cameron-catfish") عند تنفيذ البرنامج أعلاه، سنحصل على المخرجات التالية: Username: cameron-catfish Followers: 820 يحافظ تعريف الدالة على نفس ترتيب العبارات في ‎print()‎، لذلك يمكننا استخدام الوسائط المسمّاة بأيّ ترتيب نشاء. القيم الافتراضية للوسائط يمكننا إعطاء قيم افتراضية لواحد أو أكثر من المعاملات. في المثال أدناه، سنعطي للمعامل ‎followers‎ القيمة الافتراضية ‎1‎ لاستعمالها إن لم تُمرَّر هذه القيمة للدالة عند استدعائها: def profile_info(username, followers=1): print("Username: " + username) print("Followers: " + str(followers)) الآن، يمكننا استدعاء الدالة مع تعيين اسم المستخدم فقط، وسيُعيّن عدد المتابعين تلقائيًا ويأخذ القيمة 1. لكن يمكننا تغيير عدد المتابعين إن شئنا. def profile_info(username, followers=1): print("Username: " + username) print("Followers: " + str(followers)) profile_info(username="JOctopus") profile_info(username="sammyshark", followers=945) عندما ننفّذ البرنامج باستخدام الأمر ‎python profile.py‎، سنحصل على المخرجات التالية: Username: JOctopus Followers: 1 Username: sammyshark Followers: 945 تمرير قيم إلى المعاملات الافتراضية سيتخطى القيمة الافتراضية المعطاة في تعريف الدالة. إعادة قيمة كما يمكن تمرير قيم إلى الدالة، فيمكن كذلك أن تنتج الدالة قيمة وتعيدها لم استدعاها. يمكن أن تنتج الدالة قيمة، ويكونُ ذلك عبر استخدام التعليمة ‎return‎، هذه التعليمة اختيارية، وفي حال استخدامها، فستُنهِي الدالة مباشرةً عملها وتوقف تنفيذها، وتُمرَّر قيمة التعبير الذي يعقُبها إلى المُستدعي (caller). إذا لم يلي التعليمة ‎return‎ أي شيء، فستُعيد الدالة القيمةَ ‎None‎. حتى الآن، استخدمنا الدالة ‎print()‎ بدلاً من ‎return‎ في دوالنا لطباعة شيء بدلًا من إعادته. لننشئ برنامجًا يعيد متغيرًا بدلًا من طباعته الآن. في ملف نصي جديد يسمى ‎square.py‎، سننشئ برنامجًا يحسب مربع المعامل ‎x‎، ويُحيل الناتج إلى المتغير ‎y‎، ثم يعيده. سنطبع المتغير ‎result‎، والذي يساوي ناتج تنفيذ الدالة ‎square(3)‎. def square(x): y = x ** 2 return y result = square(3) print(result) لننفّذ البرنامج: python square.py سنحصل على المخرجات التالية: 9 مخرجات البرنامج هي العدد الصحيح ‎9‎ الذي أعادته الدالة وهو ما نتوقعه لو طلبنا من بايثون حساب مربع العدد 3. لفهم كيفية عمل التعليمة ‎return‎، يمكننا تعليق التعليمة ‎return‎: def square(x): y = x ** 2 # return y result = square(3) print(result) الآن، لننفّذ البرنامج مرة أخرى: python square.py سنحصل على الناتج التالي: None بدون استخدام التعليمة ‎return‎، لا يمكن للبرنامج إعادة أيّ قيمة، لذلك تُعاد القيمة الافتراضية ‎None‎. إليك مثال آخر، في برنامج ‎add_numbers.py‎ أعلاه، سنستبدل بالتعليمة ‎return‎ الدالة ‎print()‎. def add_numbers(x, y, z): a = x + y b = x + z c = y + z return a, b, c sums = add_numbers(1, 2, 3) print(sums) خارج الدالة، أحلنا إلى المتغير ‎sums‎ نتيجة استدعاء الدالة بالوسائط ‎1‎ و ‎2‎ و ‎3‎ كما فعلنا أعلاه ثم طبعنا قيمته. فلننفّذ البرنامج مرة أخرى: python add_numbers.py والناتج سيكون: (3, 4, 5) لقد حصلنا على الأعداد ‎3‎ و ‎4‎ و ‎5‎ وهي نفس المخرجات التي تلقيناها سابقًا عندما استخدمنا الدالة ‎print()‎ في الدالة. هذه المرة تمت إعادتها على هيئة صف لأنّ التعبير المرافق للتعليمة ‎return‎ يحتوي على فاصلة واحدة على الأقل. تُوقَف الدوال فورًا عندما تصل إلى التعليمة ‎return‎، سواء أعادت قيمة، أم لم تُعِد. def loop_five(): for x in range(0, 25): print(x) if x == 5: # x == 5 إيقاف الدالة عند return print("This line will not execute.") loop_five() يؤدي استخدام التعليمة ‎return‎ داخل الحلقة ‎for‎ إلى إنهاء الدالة، وبالتالي لن يتم تنفيذ السطر الموجود خارج الحلقة. لو استخدمنا بدلًا من ذلك break، فسيُنفّذ السطر ‎print()‎ الأخير من المثال السابق. نعيد التذكير أنَّ التعليمة ‎return‎ تنهي عمل الدالة، وقد تعيد قيمة إذا أعقبها تعبير. استخدام ‎main()‎ دالةً رغم أنه يمكنك في بايثون استدعاء الدالة في أسفل البرنامج، وسيتم تنفيذها (كما فعلنا في الأمثلة أعلاه)، فإنّ العديد من لغات البرمجة (مثل C++‎ و Java) تتطلب الدالة ‎main‎. إنّ تضمين دالة ‎main()‎، وإن لم يكن إلزاميًا، يمكن أن يهيكل برامج بيثون بطريقة منطقية، بحيث تضع أهم مكونات البرنامج في دالة واحدة. كما يمكن أن يجعل البرنامج أكثر مقروئية للمبرمجين غير البايثونيِّين. سنبدأ بإضافة دالة ‎main()‎ إلى برنامج ‎hello.py‎ أعلاه. سنحتفظ بالدالة ‎hello()‎، ثم نعرّف دالة ‎main()‎: def hello(): print("مرحبا بالعالم") def main(): ضمن الدالة ‎main()‎، سندرج الدالة ‎print()‎، والتي ستعُلِمنا بأننا في الدالة ‎main()‎. أيضًا سنستدعي الدالة ‎hello()‎ داخل ‎main()‎: def hello(): print("مرحبا بالعالم") def main(): print("هذه هي الدالة الرئيسية") hello() أخيرًا، في أسفل البرنامج، سنستدعي الدالة ‎main()‎: def hello(): print("مرحبا بالعالم") def main(): print("هذه هي الدالة الرئيسية.") hello() main() الآن يمكننا تنفيذ برنامجنا: python hello.py وسنحصل على المخرجات التالية: هذه هي الدالة الرئيسية. مرحبا بالعالم! لمّا استدعينا الدالة ‎hello()‎ داخل ‎main()‎، ثم نفّذنا الدالة ‎main()‎ وحدها، فقد طُبع النص مرحبا بالعالم مرة واحدة فقط، وذلك عقب السلسلة النصية التي أخبرتنا بأننا في الدالة الرئيسية. سنعمل الآن مع دوال مُتعدِّدة، لذلك من المستحسن أن تراجع نطاقات المتغيرات في المقالة: كيفية استخدام المتغيرات في بايثون. إذا عرّفت متغيرًا داخل دالة، فلا يمكنك أن تستخدم ذلك المتغير إلا ضمن تلك الدالة. لذا، إن أردت استخدام متغير ما في عدة دوال، فقد يكون من الأفضل الإعلان عنه متغيرًا عامًا (global variable). في بايثون، يعدُّ ‎'__main__'‎ اسم النطاق الذي ستُنفَّذ فيه الشيفرة العليا (top-level code). عند تنفيذ برنامج من الدخل القياسي (standard input)، أو من سكربت، أو من سطر الأوامر، سيتم ضبط ‎__name__‎ عند القيمة ‎'__main__'‎. لهذا السبب، اصطلح مطورو بايثون على استخدام الصياغة التالية: if __name__ == '__main__': # الشفرة التي ستُنفّذ لو كان هذا هو البرنامج الرئيسي هذه الصياغة تتيح استخدام ملفات بايثون إما: برامج رئيسية، مع تنفيذ ما يلي التعليمة ‎if‎، أو وحدات عادية، مع عدم تنفيذ ما يتبع التعليمة ‎if‎. سيتم تنفيذ الشفرة غير المُتضمّنة في العبارة if __name__ == '__main__'‎:‎ عند التنفيذ. إذا كنت تستخدم ملف بايثون كوحدة، فسيتم أيضًا تنفيذ الشفرة البرمجية غير المُتضمّنة في هذه العبارة عند استيراد ذلك الملف. دعنا نوسع البرنامج ‎names.py‎ أعلاه، سننشئ ملفا جديدًا يسمى ‎more_names.py‎. سنعلن في هذا البرنامج عن متغير عام، ونعدِّل الدالة ‎names()‎ الأصليَّة بشكل نقسِّم فيه التعليمات إلى دالّتين منفصلتين. ستتحقق الدالة الأولى ‎has_vowel()‎ مما إذا كانت السلسلة النصية ‎name‎ تحتوي على حرف علة (vowel). وتطبع الدالة الثانية ‎print_letters()‎ كل حرف من السلسلة النصية ‎name‎. # الإعلان عن متغير عام لاستخدامه في جميع الدوال name = str(input('أدخل اسمك:')) # يحتوي حرف علة name تعريف دالة للتحقق من أن def has_vowel(): if set('aeiou').intersection(name.lower()): print('اسمك يحتوي حرف علة') else: print('اسمك لا يحتوي حرف علة') # name المرور على حروف def print_letters(): for letter in name: print(letter) بعد ذلك، دعنا نعرّف الدالة ‎main()‎ التي سَتستدعي كلا الدّالتين ‎has_vowel()‎ و ‎print_letters()‎. # الإعلان عن متغير عام لاستخدامه في جميع الدوال name = str(input('أدخل اسمك:')) # يحتوي حرف علة name تعريف دالة للتحقق من أنّ def has_vowel(): if set('aeiou').intersection(name.lower()): print('اسمك يحتوي حرف علة') else: print('اسمك لا يحتوي حرف علة') # name المرور على حروف def print_letters(): for letter in name: print(letter) # التي ستستدعي بقية الدوال main تعريف الدالة def main(): has_vowel() print_letters() أخيرًا، سنضيف العبارة ‎if __name__ == '__main__':‎ في أسفل الملف. لقد وضعنا جميع الدوال التي نودّ تنفيذها في الدالة ‎main()‎، لذا سنستدعي الدالة ‎main()‎ بعد العبارة ‎if‎. # الإعلان عن متغير عام لاستخدامه في جميع الدوال name = str(input('أدخل اسمك:')) # يحتوي حرف علة name تعريف دالة للتحقق من أن def has_vowel(): if set('aeiou').intersection(name.lower()): print('اسمك يحتوي حرف علة') else: print('اسمك لا يحتوي حرف علة') # name المرور على حروف def print_letters(): for letter in name: print(letter) # التي ستستدعي بقية الدوال main تعريف الدالة def main(): has_vowel() print_letters() # main() تنفيذ الدالة if __name__ == '__main__': main() يمكننا الآن تنفيذ البرنامج: python more_names.py سيعرض هذا البرنامج نفس المخرجات التي عرضها البرنامج ‎names.py‎، بيْد أنّ الشفرة هنا أكثر تنظيمًا، ويمكن استخدامها بطريقة تركيبية (modular). إذا لم ترغب في الإعلان عن الدالة ‎main()‎، يمكنك بدلاً من ذلك إنهاء البرنامج كما يلي: ... if __name__ == '__main__': has_vowel() print_letters() يؤدي استخدام ‎main()‎ كدالة، واستخدام العبارة ‎if __name__ == '__main__':‎ إلى تنظيم الشيفرة البرمجية بطريقة منطقية، وجعلها أكثر مقروئية وتراكبية. خلاصة الدوال هي كتل من التعليمات البرمجية التي تُنفِّذ إجراءات معيّنة داخل البرنامج، كما تساعد على جعل الشفرة تركيبية، وقابلة لإعادة الاستخدام بالإضافة إلى أنها تنظمها وتسهل من قراءتها. لمعرفة المزيد حول كيفية جعل الشفرة تركيبية، يمكنك قراءة المقالة التالية: كيفية كتابة الوحدات في بايثون 3. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Define Functions in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضا المقالة التالية: كيفية استخدام ‎args و ‎*kwargs في بايثون 3 المقالة السابقة: كيفية استخدام تعابير break و continue و pass عند التعامل مع حلقات التكرار في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  9. لا تخلو لغة برمجة من التعليمات الشرطية التي تُنفَّذ بناءً على تحقق شرط معين، وهي تعليمات برمجية يمكنها التحكم في تنفيذ شفرات معينة بحسب تحقق شرط ما من عدمه في وقت التنفيذ. تُنفّذ تعليمات برامج بايثون من الأعلى إلى الأسفل، مع تنفيذ كل سطر بحسب ترتيبه. باستخدام التعليمات الشرطية، يمكن للبرامج التحقق من استيفاء شروط معينة، ومن ثم تنفيذ الشيفرة المقابلة. هذه بعض الأمثلة التي سنستخدم فيها التعليمات الشرطية: إن حصلت الطالبة على أكثر من 65٪ في الامتحان، فأعلن عن نجاحها؛ وإلا، فأعلن عن رسوبها إذا كان لديه مال في حسابه، فاحسب الفائدة. وإلا، فاحسب غرامة إن اشتروا 10 برتقالات أو أكثر، فاحسب خصمًا بمقدار 5٪؛ وإلا فلا تفعل تقيِّم الشيفرة الشرطية شروطًا، ثم تُنفِّذ شيفرةً بناءً على ما إذا تحققت تلك الشروط أم لا. ستتعلم في هذا الدرس كيفية كتابة التعليمات الشرطية في بايثون. التعليمة if سنبدأ بالتعليمة ‎if‎، والتي تتحقق مما إذا تحقق شرط محدَّد أم لا، وفي حال تحقق الشرط، فستنفَّذ الشيفرة المقابلة له. لنبدأ بأمثلة عملية توضح ذلك. افتح ملفًا، واكتب الشيفرة التالية: grade = 70 if grade >= 65: print("درجة النجاح") أعطينا للمتغير ‎grade‎ القيمة ‎70‎. ثم استخدمنا التعليمة ‎if‎ لتقييم ما إذا كان المتغير grade أكبر من (‎>=‎) أو يساوي ‎65‎. وفي تلك الحالة، سيطبع البرنامج السلسلة النصية التالية: درجة النجاح. احفظ البرنامج بالاسم ‎grade.py‎، ثم نفّذه في بيئة البرمجة المحلية من نافذة الطرفية باستخدام الأمر ‎python grade.py‎. في هذه الحالة، الدرجة 70 تلبي الشرط، لأنّها أكبر من 65، لذلك ستحصل على المخرجات التالية عند تنفيذ البرنامج: درجة النجاح لنغيّر الآن نتيجة هذا البرنامج عبر تغيير قيمة المتغير ‎grade‎ إلى ‎60‎: grade = 60 if grade >= 65: print("درجة النجاح") بعد حفظ وتنفيذ الشيفرة، لن نحصل على أي مخرجات، لأنّ الشرط لم يتحقق، ولم نأمر البرنامج بتنفيذ تعليمة أخرى. كمثال آخر، دعنا نتحقق مما إذا كان رصيد الحساب المصرفي أقل من 0. لننشئ ملفا باسم ‎account.py‎، ونكتب البرنامج التالي: balance = -5 if balance < 0: print("الحساب فارغ، أضف مبلغا الآن، أو ستحصل على غرامة.") عند تنفيذ البرنامج باستخدام ‎python account.py‎، سنحصل على المخرجات التالية: الحساب فارغ، أضف مبلغًا الآن، أو ستحصل على غرامة. أعطينا للمتغير ‎balance‎ القيمة ‎-5‎، وهي أقل من 0 في البرنامج السابق. ولمَّا كان الرصيد مستوفيًا لشرط التعليمة ‎if‎ (أي ‎balance < 0‎)، فسنحصل على سلسلة نصية في المخرجات بمجرد حفظ الشيفرة وتنفيذها. مرة أخرى، لو غيرنا الرصيد إلى القيمة 0 أو إلى عدد موجب، فلن نحصل على أيّ مخرجات. التعليمة Else قد تريد من البرنامج أن يفعل شيئًا ما في حال عدم تحقق شرط التعليمة ‎if‎. في المثال أعلاه، نريد طباعة مخرجات في حال النجاح والرسوب. ولفعل ذلك، سنضيف التعليمة ‎else‎ إلى شرط الدرجة أعلاه وفق الصياغة التالية: grade = 60 if grade >= 65: print("درجة النجاح") else: print("درجة الرسوب") قيمة المتغير ‎‎grade‎‎ تساوي ‎60‎، لذلك فشرط التعليمة ‎if‎ غير متحقق، وبالتالي فإنّ البرنامج لن يطبع ‎درجة النجاح‎. تخبر التعليمة ‎else‎ البرنامجَ أنّ عليه طباعة السلسلة النصية "درجة الرسوب". عندما نحفظ البرنامج وننفّذه، سنحصل على المخرجات التالية: درجة الرسوب إذا عدّلنا البرنامج وأعطينا المتغيرَ grade القيمة ‎65‎ أو أعلى منها، فسنحصل بدلاً من ذلك على الناتج ‎درجة النجاح‎. لإضافة التعليمة ‎else‎ إلى مثال الحساب المصرفي، سنعيد كتابة الشيفرة كما يلي: balance = 522 if balance < 0: print("الحساب فارغ، أضف مبلغا الآن، أو ستحصل على غرامة.") else: print("رصيدك أكبر من 0.") سنحصل على المخرجات التالية: رصيدك أكبر من 0. هنا، غيّرنا قيمة المتغير ‎balance‎ إلى عدد موجب لكي تُنفَّذ الشيفرة المقابلة للتعليمة ‎else‎. إن أردت تنفيذ الشيفرة المقابلة للتعليمة ‎if‎، غيِّر القيمة إلى عدد سالب. من خلال دمج العبارتين ‎if‎ و ‎else‎، فأنت تنشئ تعليمة شرطية مزدوجة، والتي ستجعل الحاسوب ينفذ شيفرة برمجية معينة سواء تم استيفاء شرط ‎if‎ أم لا. التعليمة Else if حتى الآن، عملنا على تعليمات شرطية ثنائية، أي إن تحقق الشرط، فنفذ شيفرة ما، وإلا، فنفِّذ شيفرة أخرى فقط. لكن في بعض الحالات، قد تريد برنامجًا يتحقق من عدة حالات شرطية. ولأجل هذا، سوف نستخدم التعليمة Else if، والتي تُكتب في بايثون هكذا ‎elif‎. تشبه التعليمة ‎elif‎ - أو Else if - التعليمة ‎if‎، ومهمتها التحقق من شرط إضافي. في برنامج الحساب المصرفي، قد نرغب في الحصول على ثلاثة مخرجات مختلفة مقابلة لثلاث حالات مختلفة: الرصيد أقل من 0 الرصيد يساوي 0 الرصيد أعلى من 0 ستوضع التعليمة ‎elif‎ بين التعليمة ‎if‎ والتعليمة ‎else‎ كما يلي: . . . if balance < 0: print("الحساب فارغ، أضف مبلغا الآن، أو ستحصل على غرامة.") elif balance == 0: print("الرصيد يساوي 0، أضف مبلغًا قريبًا.") else: print("رصيدك أكبر من 0.") الآن، هناك ثلاثة مخرجات محتملة يمكن أن تُطبع عند تنفيذ البرنامج: إن كان المتغير ‎balance‎ يساوي ‎0‎، فسنحصل على المخرجات من التعليمة ‎elif‎ (أي ‎الرصيد يساوي 0، أضف مبلغًا قريبًا.‎). إذا ضُبِط المتغير ‎balance‎ عند عدد موجب، فسوف نحصل على المخرجات من التعليمة ‎else‎ (أي ‎رصيدك أكبر من 0.‎). إذا ضُبِط المتغير ‎balance‎ عند عدد سالب، فسنحصل على المخرجات من التعليمة ‎if‎ (أي ‎الحساب فارغ، أضف مبلغا الآن، أو ستحصل على غرامة‎). ماذا لو أردنا أن نأخذ بالحسبان أكثر من ثلاثة احتمالات؟ يمكننا كتابة عدة تعليمات ‎elif‎ في الشيفرة البرمجية. لنُعِد كتابة البرنامج ‎grade.py‎ بحيث يقابل كل نطاق من الدرجات علامة محددة: 90 أو أعلى تكافئ العلامة أ 80-89 تعادل العلامة ب 70-79 تعادل العلامة ج 65-69 تعادل العلامة د 64 أو أقل تكافئ العلامة ه سنحتاج لتنفيذ هذه الشيفرة إلى تعليمة ‎if‎ واحد، وثلاث تعليمات ‎elif‎، وتعليمة ‎else‎ تعالج جميع الحالات الأخرى. دعنا نعيد كتابة الشيفرة من المثال أعلاه لطباعة سلسلة نصية مقابلة لكل علامة. يمكننا الإبقاء على التعليمة ‎else‎ كما هي. . . . if grade >= 90: print("العلامة أ") elif grade >=80: print("العلامة ب") elif grade >=70: print("العلامة ج") elif grade >= 65: print("العلامة د") else: print("درجة الرسوب") تُنفّذ التعليمات ‎elif‎ بالترتيب. هذا البرنامج سيكمل الخطوات التالية: إذا كانت الدرجة أكبر من 90، فسيطبع البرنامجُ ‎العلامة أ‎، وإذا كانت الدرجة أقل من 90، فسيمرّ البرنامج إلى التعليمة التالية … إذا كانت الدرجة أكبر من أو تساوي 80، فسيطبع البرنامجُ ‎العلامة ب‎، إذا كانت الدرجة تساوي 79 أو أقل، فسيمرّ البرنامج إلى التعليمة التالية … إذا كانت الدرجة أكبر من أو تساوي 70، فسيطبعُ البرنامجُ ‎العلامة ج‎، إذا كانت الدرجة تساوي 69 أو أقل، فسيمرّ البرنامج إلى التعليمة التالية … إذا كانت الدرجة أكبر من أو تساوي 65، فسيطبع البرنامجُ ‎العلامة د‎، وإذا كانت الدرجة تساوي 64 أو أقل، فسيمرّ البرنامج إلى التعليمة التالية … سيطبع البرنامج ‎درجة الرسوب‎، لأنه لم يتم استيفاء أيِّ من الشروط المذكورة أعلاه. تعليمات if المتشعبة بعد أن تتعود على التعليمات ‎if‎ و ‎elif‎ و ‎else‎، يمكنك الانتقال إلى التعليمات الشرطية المتشعبة (nested conditional statements). يمكننا استخدام تعليمات ‎if‎ المتشعبة في الحالات التي نريد فيها التحقق من شرط ثانوي بعد التأكد من تحقق الشرط الرئيسي. لهذا، يمكننا حشر تعليمة if-else داخل تعليمة if-else أخرى. لنلقِ نظرة على صياغة ‎if‎ المتشعبة: if statement1: # الخارجية if تعليمة print("true") if nested_statement: # المتشعبة if تعليمة print("yes") else: # المتشعبة else تعليمة print("no") else: # الخارجية else تعليمة print("false") هناك عدة مخرجات محتملة لهذه الشيفرة: إذا كانت ‎statement1‎ صحيحة، فسيتحقق البرنامج مما إذا كانت ‎nested_statement‎ صحيحة أيضًا. إذا كانت كلتا الحالتين صحيحتان، فسنحصل على المخرجات التالية: true yes ولكن إن كانت ‎statement1‎ صحيحة، و ‎nested_statement‎ خاطئة، فسنحصل على المخرجات التالية: true no وإذا كانت ‎statement1‎ خاطئة، فلن تُنفّذ تعليمة if-else المتشعبة على أيّ حال، لذلك ستُنفّذ التعليمة ‎else‎ وحدها، والمخرجات ستكون: false يمكن أيضًا استخدام عدة تعليمات ‎if‎ متشعبة في الشيفرة: if statement1: # الخارجية if print("hello world") if nested_statement1: # المتشعبة الأولى if print("yes") elif nested_statement2: # المتشعبة الأولى elif print("maybe") else: #المتشعبة الأولى else print("no") elif statement2: # الخارجية elif print("hello galaxy") if nested_statement3: # المتشعبة الثانية if print("yes") elif nested_statement4: # المتشعبة الثانية elif print("maybe") else: # المتشعبة الثانية else print("no") else: # الخارجية else statement("مرحبا") في الشيفرة البرمجية أعلاه، هناك تعليمات ‎if‎ و ‎elif‎ متشعبة داخل كل تعليمات ‎if‎. هذا سيفسح المجال لمزيد من الخيارات في كل حالة. دعنا نلقي نظرة على مثال لتعليمات ‎if‎ متشعبة في البرنامج ‎grade.py‎. يمكننا التحقق أولًا مما إذا كان الطالب قد حقق درجة النجاح (أكبر من أو تساوي 65٪)، ثم نحدد العلامة المقابلة للدرجة. إذا لم يحقق الطالب درجة النجاح، فلا داعي للبحث عن العلامة المقابلة للدرجة، وبدلًا من ذلك، يمكن أن نجعل البرنامج يطبع سلسلة نصية فيها إعلان عن رسوب الطالب. ستبدو الشيفرة المعدلة كما يلي: . . . if grade >= 65: print("درجة النجاح:") if grade >= 90: print("أ") elif grade >=80: print("ب") elif grade >=70: print("ج") elif grade >= 65: print("د") else: print("درجة الرسوب") إذا أعطينا للمتغير ‎grade‎ القيمة ‎92‎، فسيُستوفى الشرط الأول، وسيَطبع البرنامجُ ‎درجة النجاح:‎. بعد ذلك، سيتحقق مما إذا كانت الدرجة أكبر من أو تساوي 90، وبما أنّ هذا الشرط مستوفًى أيضًا، فستُطبع ‎أ‎. أما إذا أعطينا للمتغير ‎grade‎ القيمة ‎60‎، فلن يتم استيفاء الشرط الأول، لذلك سيتخطى البرنامج تعليمات ‎if‎ المتشعبة، وينتقل إلى التعليمة ‎else‎، ويطبع ‎درجة الرسوب‎. يمكننا بالطبع إضافة المزيد من الخيارات، واستخدام طبقة ثانية من تعليمات if المتشعبة. ربما نود إضافة الدرجات التفصيلية أ+‎ و أ و أ-‎. يمكننا القيام بذلك عن طريق التحقق أولًا من اجتياز درجة النجاح، ثم التحقق مما إذا كانت الدرجة تساوي 90 أو أعلى، ثم التحقق مما إذا كانت الدرجة تتجاوز 96، وفي تلك الحالة ستقابل العلامة ‎أ+. إليك المثال التالي: . . . if grade >= 65: print("درجة النجاح:") if grade >= 90: if grade > 96: print("أ+") elif grade > 93 and grade <= 96: print("أ") elif grade >= 90: print("أ-") . . . في الشيفرة أعلاه، في حال تعيين المتغير ‎grade‎ عند القيمة ‎96‎، سيقوم البرنامج بما يلي: التحقق مما إذا كانت الدرجة أكبر من أو تساوي 65 (صحيح) طباعة ‎درجة النجاح:‎ التحقق مما إذا كانت الدرجة أكبر من أو تساوي 90 (صحيح) التحقق مما إذا كانت الدرجة أكبر من 96 (خطأ) التحقق مما إذا كانت الدرجة أكبر من 93، وأقل من أو تساوي 96 (صحيح) طباعة أ‎ تجاوز التعليمات الشرطية المتشعبة وتنفيذ باقي الشيفرة ستكون مخرجات البرنامج في حال كانت الدرجة تساوي 96 كالتالي: درجة النجاح: أ تساعد تعليمات ‎if‎ المتشعبة على إضافة عدة مستويات من الشروط الفرعية إلى الشيفرة. خلاصة ستتحكم باستخدام التعليمات الشرطية، مثل التعليمة ‎if‎، في مسار البرنامج أي تدفق تنفيذ الشيفرة. تطلب التعليمات الشرطية من البرنامج التحقق من استيفاء شرط معين من عدمه. وإذا تم استيفاء الشرط، فستُنفّذ شيفرة معينة، وإلا فسيستمر البرنامج وينتقل إلى الأسطر التالية. يمكنك الدمج بين التعليمات الشرطية والمعاملات المنطقية، بما فيها and و or، واستخدام التعليمات الشرطية مع الحلقات التكرارية. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Write Conditional Statements in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية إنشاء حلقات تكرار while في بايثون 3 المقالة السابقة: كيفية كتابة الوحدات في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  10. وحدات (modules) بايثون هي ملفات ‎.py‎ تحتوي شفرة بايثون، ويمكن التعامل مع أيّ ملف بايثون على أنه وحدة. تتوفر بعض الوحدات في مكتبة بايثون القياسية، والتي تُثبّت تلقائيًا مع بايثون. يمكن لبعضها الآخر أن يُثبّت عبر مدير الحزم pip. بالإضافة إلى ذلك، يمكنك إنشاء وحدات بايثون خاصة بك، لأنّ الوحدات هي مجرد ملفات ‎.py‎. سيرشدك هذا الدرس إلى كيفية كتابة وحدات بايثون لاستخدامها في ملفات البرمجة الأخرى. كتابة الوحدات واستيرادها كتابة الوحدات مشابه لكتابة أي ملف بايثون آخر. يمكن أن تحتوي الوحدات على تعريفات الدوال والأصناف والمتغيرات التي يمكن استخدامها بعد ذلك في برامج بايثون الأخرى. سنشئ من بيئة البرمجة الحالية الخاصة ببايثون 3 أو بيئة البرمجة المستندة إلى الخادم ملفًّا باسم ‎hello.py‎، والذي سنتستورده لاحقًا من ملف آخر. في البدء، سننشئ دالة تطبع العبارة مرحبا بالعالم!: # تعريف دالة def world(): print("مرحبا بالعالم!") إذا نفّذنا البرنامج في سطر الأوامر باستخدام ‎python hello.py‎، فلن يحدث شيء، لأننا لم نطلب من البرنامج فعل أي شيء. لننشئ ملفًا ثانيًا في نفس المجلد (أي بجانب الملف السابق) باسم ‎main_program.py‎ حتى نتمكن من استيراد الوحدة التي أنشأناها للتو، ومن ثم استدعاء الدالة. يجب أن يكون هذا الملف في نفس المجلد حتى تعرف بايثون موضع الوحدة، لأنها ليست وحدة مُضمّنة. # hello استيراد الوحدة import hello # استدعاء الدالة hello.world() نظرًا لأننا استوردنا الوحدة، نحتاج إلى استدعاء الدالة من خلال التأشير إلى اسم الوحدة بالصياغة النقطية (dot notation). يمكننا بدلًا من ذلك استيراد دالة محدَّدة من الوحدة بالتعليمة ‎from hello import world‎، واستدعاء تلك الدالة بالشكل ‎world()‎ كما تعلمنا ذلك من الدرس السابق. الآن، يمكننا تنفيذ البرنامج من سطر الأوامر: python main_program.py سنحصل على المخرجات التالية: مرحبا بالعالم! لنرى كيف يمكننا استخدام المتغيرات في الوحدات، دعنا نضيف تعريفًا لمتغير في الملف ‎hello.py‎: # تعريف دالة def world(): print("مرحبا بالعالم!") # تعريف المتغير shark = "Sammy" بعد ذلك، سنستدعي المتغير داخل الدالة ‎print()‎ في الملف ‎main_program.py‎: # hello استيراد الوحدة import hello # استدعاء الدالة hello.world() # طباعة المتغير print(hello.shark) بمجرد تنفيذ البرنامج، سنحصل على المخرجات التالية: مرحبا بالعالم! Sammy أخيرًا، دعنا نعرّف صنفًا في الملف ‎hello.py‎. سننشئ الصنف ‎Octopus‎، والذي يحتوي على الخاصيتين ‎name‎ و ‎color‎، إضافة إلى دالة تطبع الخاصيات عند استدعائها. # تعريف الدالة def world(): print("مرحبا بالعالم!") # تعريف المتغير shark = "Sammy" # تعريف الصنف class Octopus: def __init__(self, name, color): self.color = color self.name = name def tell_me_about_the_octopus(self): print("This octopus is " + self.color + ".") print(self.name + " is the octopus's name.") سنضيف الآن الصنفَ إلى نهاية الملف ‎main_program.py‎: # hello استيراد الوحدة import hello # استدعاء الدالة hello.world() # طباعة المتغير print(hello.shark) # استدعاء الصنف jesse = hello.Octopus("Jesse", "orange") jesse.tell_me_about_the_octopus() بمجرد استدعاء الصنف Octopus باستخدام ‎hello.Octopus()‎، يمكننا الوصول إلى دوال وخاصيات الصنف من فضاء الأسماء الخاص بالملف ‎main_program.py‎. يتيح لنا هذا كتابة ‎jesse.tell_me_about_the_octopus()‎ في السطر الأخير دون استدعاء ‎hello‎. يمكننا أيضًا، على سبيل المثال، استدعاء إحدى خاصيات الصنف، مثل ‎jesse.color‎، دون الرجوع إلى اسم الوحدة ‎hello‎. سنحصل عند تنفيذ البرنامج على المخرجات التالية: مرحبا بالعالم! Sammy This octopus is orange. Jesse is the octopus's name. من المهم أن تضع في الحسبان أنه على الرغم من أنّ الوحدات غالبًا ما تضم تعريفات، إلا أنها يمكن أيضًا أن تقدم شفرات برمجية. لتوضيح هذا، دعنا نعيد كتابة الملف ‎hello.py‎ لنجعله يقدم دالة ‎world()‎: # تعريف دالة def world(): print("مرحبا بالعالم!") # استدعاء الدالة داخل الوحدة world() لقد حذفنا أيضًا التعريفات الأخرى في الملف. الآن، في الملف ‎main_program.py‎، سنحذف كل الأسطر باستثناء عبارة الاستيراد: # hello استيراد الوحدة import hello عند تنفيذ ‎main_program.py‎، سنحصل على المخرجات التالية: مرحبا بالعالم! هذا لأنّ الوحدة ‎hello‎ قدمت الدالة ‎world()‎، والتي مُرِّرت بعد ذلك إلى ‎main_program.py‎ لتُنفّذ مع السكربت ‎main_program.py‎. الوحدة هي ملف بايثون مؤلف من تعريفات و شيفرات برمجية يمكن الاستفادة منها في ملفات بايثون الأخرى. الوصول إلى الوحدات من مجلد آخر قد تكون الوحدات مفيدة لأكثر من مشروع واحد، وفي هذه الحالة، لن يكون من الحكمة الاحتفاظ بالوحدة في مجلد مرتبط بمشروع خاص. إذا أردت استخدام وحدة من مجلد آخر غير المجلد الذي يحوي البرنامج الرئيسي، فأمامك عدة خيارات سنسردها فيما يلي. التعرف تلقائيًا على مسار الوحدة أحد الخيارات هو استدعاء مسار الوحدة من الملفات البرمجية التي تستخدم تلك الوحدة. يُعد هذا حلًّا مؤقتًا يمكن استخدامه أثناء عملية التطوير، لأنه لا يجعل الوحدة متاحة على مستوى النظام بأكمله. لإلحاق مسار وحدة بملف برمجي آخر، ستبدأ باستيراد الوحدة ‎sys‎، إلى جانب الوحدات الأخرى التي ترغب في استخدامها في ملف البرنامج الرئيسي. تعد الوحدة ‎sys‎ جزءًا من مكتبة بايثون القياسية، وتوفر معاملات ودوال نظامية يمكنك استخدامها في برنامجك لتعيين مسار الوحدة التي ترغب في تقديمها. على سبيل المثال، لنقل أننا نقلنا الملف ‎hello.py‎ إلى المسار ‎/usr/sammy/‎، بينما يوجد الملف ‎main_program.py‎ في مجلد آخر. في الملف ‎main_program.py‎، ما يزال بإمكاننا استيراد الوحدة ‎hello‎ عن طريق استيراد الوحدة ‎sys‎، ثم إضافة المسار ‎/usr/sammy/‎ إلى المسارات التي يبحث بايثون فيها عن الملفات. import sys sys.path.append('/usr/sammy/') import hello ... إن عيّنت مسار الملف ‎hello.py‎ بشكل صحيح، فسيكون بمقدورك تنفيذ الملف ‎main_program.py‎ دون أيّ أخطاء، وستحصل على نفس المخرجات التي حصلنا عليها أعلاه عندما كان ‎hello.py‎ في نفس المجلد. إضافة الوحدة إلى مسار بايثون الخيار الثاني هو إضافة الوحدة إلى المسار الذي يبحث فيه بايثون عن الوحدات والحزم. هذا حل أفضل وأدوم، لأنه يجعل الوحدة متاحة على نطاق البيئة، أو على مستوى النظام. لمعرفة المسار الذي يبحث فيه بايثون، شغِّل مترجم (interpreter) بايثون من بيئة البرمجة خاصتك: python بعد ذلك، استورد الوحدة ‎sys‎: import sys ثم اطلب من بايثون طباعة مسار النظام: print(sys.path) ستحصل على بعض المخرجات، وسيُطبع مسار نظام واحد على الأقل. إذا كنت تعمل في بيئة برمجة، فقد تتلقى العديد منها. سيكون عليك البحث عن المسارات الموجودة في البيئة التي تستخدمها حاليًا، ولكن قد ترغب أيضًا في إضافة الوحدة إلى مسار النظام الرئيسي لبايثون. النتيجة ستكون مشابهة لما يلي: '/usr/sammy/my_env/lib/python3.5/site-packages' يمكنك الآن نقل الملف ‎hello.py‎ إلى هذا المجلد. بعد ذلك، يمكنك استيراد الوحدة ‎hello‎ كالمعتاد: import hello ... عند تنفيذ البرنامج، يُفترض ألا يحدث أيّ خطأ. يضمن لك تعديل مسار الوحدة إمكانية الوصول إليها مهما كان المجلد الذي تعمل فيه. هذا مفيد خاصة في حال كنت تعمل على عدة مشاريع تشير إلى الوحدة نفسها. خلاصة إنّ كتابة وحدات بايثون لا يختلف عن كتابة أيّ ملف بايثون آخر. غطينا في هذه المقالة كيفية كتابة التعاريف في الوحدات، وكيفية استخدامها في ملف بايثون آخر، وعرضنا بعض الخيارات حول المواضِع التي يمكن أن تحفظ فيها الوحدة لتجعلها متاحة. يمكنك تعلم المزيد حول تثبيت الوحدات واستيرادها من الدرس السابق: [كيفية استيراد الوحدات في بايثون 3](). هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Write Modules in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية كتابة العبارات الشرطية في بايثون 3 المقالة السابقة: كيفية استيراد الوحدات في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  11. توفر لغة بايثون مجموعة متنوعة من الدوال المضمّنة مثل: print()‎‎: تطبع التعابير المُمرَّرة إليها في مجرى الخرج abs()‎‎: تُعيد القيمة المطلقة للعدد int()‎‎: تحوِّل نوع بيانات إلى عدد صحيح len()‎‎: تُعيد طول تسلسل أو مجموعة هذه الدوال المضمّنة وغيرها مفيدة، لكنها محدودة، لهذا يستخدم المطورون الوحدات (modules) لتطوير برامج أكثر تعقيدًا. الوحدات (Modules) هي ملفات بايثون ذات امتداد ‎.py، والتي تحوي شيفرات بايثون. يمكن التعامل مع أيّ ملف بايثون على أنه وحدة. مثلًا، إن كان هناك ملف بايثون يسمى hello.py، فسيكون اسم الوحدة المقابلة له hello، والذي يمكن استيراده في ملفات بايثون الأخرى، أو استخدامه في مترجم (interpreter) سطر أوامر بايثون. ستتعلم في المقالة التالية كيفية إنشاء الوحدات. يمكن للوحدات أن تعرِّف دوالًا وأصنافًا ومتغيرات يمكن الرجوع إليها من ملفات بايثون الأخرى، أو من مترجم سطر أوامر بايثون. في بايثون، يمكنك الوصول إلى الوحدات باستخدام العبارة import. عند فعل ذلك، ستُنفَّذ شيفرة الوحدة، مع الاحتفاظ بنطاقات (scopes) التعريفات حتى تكون متاحة في ملفك الحالي. عندما تستورد بايثون وحدةً باسم hello على سبيل المثال، فسيبحث المترجم أولًا عن وحدة مضمّنة باسم hello. فإن لم يجد، فسيبحث عن ملف يسمى hello.py في قائمة من المجلدات يحددها المتغير sys.path. سيرشدك هذا الدرس إلى كيفية البحث عن الوحدات وتثبيتها، واستيرادها، وإعادة تسميتها (aliasing). تثبيت الوحدات هناك عدد من الوحدات المضمنة في مكتبة بايثون القياسية التي تحتوي على العديد من الوحدات التي توفر الكثير من وظائف النظام، أو توفر حلولًا قياسية. مكتبة بايثون القياسية تأتي مع كل توزيعات بايثون. للتحقق من أنّ وحدات بايثون جاهزة للعمل، ادخل إلى بيئة برمجة بايثون 3 المحلية، أو بيئة البرمجة المستندة إلى الخادم، وشغّل مترجم بايثون في سطر الأوامر على النحو التالي: (my_env) sammy@ubuntu:~/environment$ python من داخل المترجم، يمكنك تنفيذ العبارة import مع اسم الوحدة للتأكد من أنّها جاهزة: import math لمّا كانت math وحدة مضمّنة، فينبغي أن يُكمل المترجم المهمة دون أي مشاكل، ثم يعود إلى المحث (prompt). هذا يعني أنك لست بحاجة إلى فعل أيّ شيء للبدء في استخدام الوحدة math. لننفِّذ الآن العبارة import مع وحدة قد لا تكون مُثبّتة عندك، مثل matplotlib، وهي مكتبة للرسم ثنائي الأبعاد: import matplotlib إذا لم تكن matplotlib مثبتة، فستتلقّى خطأً مثل هذا: ImportError: No module named 'matplotlib' يمكنك إيقاف مترجم بايثون بالضغط على CTRL + D، ثم تثبيت الوحدة matplotlib عبر pip بتنفيذ الأمر التالي: (my_env) sammy@ubuntu:~/environment$ pip install matplotlib بمجرد تثبيتها، يمكنك استيراد matplotlib من مترجم بايثون باستخدام import matplotlib، ولن يحدث أيّ خطأ. استيراد الوحدات للاستفادة من الدوال الموجودة في الوحدة، ستحتاج إلى استيراد الوحدة عبر التعليمة import. تتألف التعليمة import من الكلمة المفتاحية import معقوبة باسم الوحدة. يُصرَّح عن عملية استيراد الوحدات في أعلى ملفات بايثون، قبل الأسطر التوجيهية (shebang lines أي الأسطر التي تبدأ بـ ‎#!‎)، أو التعليقات العامة. لذلك، سنستورد في ملف برنامج بايثون my_rand_int.py الوحدة random لتوليد أعداد عشوائية على النحو التالي: import random عندما نستورد وحدة، فإننا نجعلها متاحة في برنامجنا الحالي كفضاء أسماء (namespace) منفصل. هذا يعني أنه سيتعيّن علينا الرجوع إلى الدالة باستخدام الصياغة النقطية (dot notation) على النحو التالي [module].[function]. عمليًا، باستخدام مثال الوحدة random، ستبدو الشفرة كما يلي: random.randint()‎‎‎: تستدعي الدالة لإعادة عدد صحيح عشوائي، أو random.randrange()‎‎: تستدعي الدالة لإعادة عنصر عشوائي من نطاق محدد. دعنا ننشئ حلقة for لتوضيح كيفية استدعاء دالة من الوحدة random ضمن البرنامج my_rand_int.py: import random for i in range(10): print(random.randint(1, 25)) يستورد هذا البرنامج الصغير الوحدة random في السطر الأول، ثم ينتقل إلى الحلقة for التي ستمر على 10 عناصر. داخل الحلقة، سيطبع البرنامج عددًا صحيحًا عشوائيًا من المجال 1 إلى 25 (مشمول). يُمرّّر العددان الصحيحان 1 و 25 إلى random.randint()‎‎ كمعاملين. عند تنفيذ البرنامج باستخدام الأمرpython my_rand_int.py، ستظهر 10 أعداد صحيحة عشوائية في المخرجات. نظرًا لأنّ هذه العناصر عشوائية، فستحصل على الأرجح على أعداد مختلفة في كل مرة تنفّذ فيها البرنامج، لكنها عمومًا ستبدو كما يلي: 6 9 1 14 3 22 10 1 15 9 الأعداد الصحيحة كلها محصورة بين 1 و 25. إذا كنت ترغب في استخدام دوال من أكثر من وحدة، يمكنك ذلك عن طريق إضافة عدة تعليمات استيراد: import random import math قد تصادف برامج تستورد عدة وحدات مفصولة بفواصل - مثل import random, math - ولكنّ هذا لا يتوافق مع دليل التنسيق PEP 8. للاستفادة من الوحدة الإضافية، يمكننا إضافة الثابت pi من الوحدة math إلى برنامجنا، وتقليل عدد الأعداد الصحيحة العشوائية المطبوعة: import random import math for i in range(5): print(random.randint(1, 25)) print(math.pi) الآن، عند تنفيذ البرنامج، سنحصل على مخرجات على الشكل التالي، مع تقريب للعدد pi في السطر الأخير: 18 10 7 13 10 3.141592653589793 تتيح لك التعليمة import استيراد وحدة واحدة أو أكثر إلى برامجك، وهذ يمكّنك من الاستفادة مما تحويها تلك الوحدات. استخدام الصياغة from ... import للإشارة إلى عناصر من وحدة مستوردة ضمن فضاء الأسماء، يمكنك استخدام التعليمة from ... import. عندما تستورد الوحدات بهذه الطريقة، سيكون بمقدورك الرجوع إلى الدوال بأسمائها فقط، بدلًا من استخدام الصياغة النقطية. في هذه الصياغة، يمكنك تحديد التعريفات التي تود الإشارة إليها مباشرة. في بعض البرامج، قد ترى العبارة * from ... import، إذ تشير العلامة * إلى جميع العناصر الموجودة في الوحدة، ولكنّ هذه الصياغة غير معتمدة في PEP 8. سنحاول في البداية استيراد دالة واحدة من الوحدة random، وهي randint()‎‎: from random import randint هنا، نستدعي أولًا الكلمة المفتاحية from، ثم random. بعد ذلك، نستخدم الكلمة المفتاحية import، ونستدعي الدالة المحددة التي نودّ استخدامها. الآن، عندما نرغب في استخدام هذه الدالة في برنامجنا، لن نستدعي الدالة وفق الصياغة النقطية، random.randint()‎‎، ولكن سنستدعيها باسمها مباشرةً، أي randint()‎‎: from random import randint for i in range(10): print(randint(1, 25)) عند تنفيذ البرنامج، ستتلقى مخرجات مشابهة لما تلقيته مسبقًا. يتيح لنا استخدام from ... import الرجوع إلى العناصر المعرّفة في الوحدة من فضاء الأسماء الخاص ببرنامجنا، مما يتيح لنا تجنب استخدام الصياغة النقطية الطويلة. الأسماء المستعارة في الوحدات يمكن تعديل أسماء الوحدات ودوالها داخل بايثون باستخدام الكلمة المفتاحية as. قد ترغب في تغيير اسم ما لأنك تستخدمه سلفًا في برنامجك، أو أنه مستخدم في وحدة أخرى مستوردة، أو قد ترغب في اختصار اسم طويل تستخدمه كثيرًا. يمكنك ذلك عبر الصياغة التالية: import [module] as [another_name] لنعدّل اسم الوحدة math في ملف البرنامج my_math.py. سنغيّر اسم الوحدة math إلى m من أجل اختصاره. سيبدو برنامجنا المعدل كالتالي: import math as m print(m.pi) print(m.e) سنشير داخل البرنامج إلى الثابت pi بالتعبير m.pi، بدلًا من math.pi. يشيع في بعض الوحدات استخدام أسماء مستعارة (aliases) محدَّدة. فمثلًا، يدعو التوثيق الرسمي للوحدة matplotlib.pyplot إلى استخدام الاسم المستعار plt: import matplotlib.pyplot as plt يسمح هذا للمبرمجين بإلحاق الكلمة القصيرة plt بأي دالة متاحة داخل الوحدة، كما هو الحال في plt.show()‎‎. خلاصة يسمح مفهوم الوحدات باستدعاء دوال غير مضمّنة في بايثون. فبعض الوحدات مُثبّتة كجزء من بايثون، وبعضها سنثبّتها عبر pip. يتيح لنا استخدام الوحدات توسيع برامجنا وتقويتها، لأنها تضع تحت تصرّفنا شفرات جاهزة للاستخدام. يمكننا أيضًا إنشاء وحدات خاصة بنا، لنستخدمها نحن، أو المبرمجون الآخرون وهذا ما سنتعرف عليه في المقال التالي. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Import Modules in Python 3 لصاحبته Lisa Tagliaferri. اقرأ أيضًا المقالة التالية: كيفية كتابة الوحدات في بايثون 3 المقالة السابقة: فهم القواميس في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  12. القاموس هو نوع مُضمَّن في بايثون. تربط القواميس مفاتيح بقيم على هيئة أزواج، وهذه الأزواج مفيدة لتخزين البيانات في بايثون. تستخدم القواميس عادةً لتخزين البيانات المترابطة، مثل المعلومات المرتبطة برقم تعريف، أو ملفات تعريف المستخدم، وتُنشأ باستخدام الأقواس المعقوصة {}. تبدو القواميس على الشكل التالي: sammy = {'username': 'sammy-shark', 'online': True, 'followers': 987} بالإضافة إلى القوسين المعقوصين، لاحظ وجود النقطتين الرأسيتين (:) في القاموس. الكلمات الموجودة على يسار النقطتين الرأسيتين هي المفاتيح التي قد تكون أيَّ نوع بيانات غير قابل للتغيير. المفاتيح في القاموس أعلاه هي: username online *followers المفاتيح في المثال أعلاه عبارة عن سلاسل نصية. تمثِّل الكلمات الموجودة على يمين النقطتين «القيم». يمكن أن تتألف القيم من أي نوع من البيانات. القيم في القاموس أعلاه هي: sammy-shark True *987 قيم القاموس أعلاه هي إمَّا سلاسل نصية أو قيم منطقية أو أعداد صحيحة. سنطبع الآن القاموس sammy: print(sammy) # {'username': 'sammy-shark', 'followers': 987, 'online': True} نلاحظ بالنظر إلى المخرجات تغير ترتيب الأزواج قيمة-مفتاح (key-value). في بايثون 3.5 وما قبله، إذ القواميس غير مرتبة. لكن ابتداءً من بايثون 3.6، صارت القواميس مرتبةً. بغض النظر عما إذا كان القاموس مرتبًا أم لا، ستظل الأزواج قيمة-مفتاح كما هي، وهذا سيمكّنك من الوصول إلى البيانات بناء على ترابطاتها. الوصول إلى عناصر القاموس يمكننا الوصول إلى قيم محدَّدة في القاموس بالرجوع إلى المفاتيح المرتبطة بها ويمكن أيضًا الاستعانة ببعض التوابع الجاهزة للوصول إلى القيم أو المفاتيح أو كليهما. الوصول إلى عناصر القاموس باستخدام المفاتيح إذا أردنا الحصول على اسم المستخدم في Sammy، فيمكننا ذلك عن طريق استدعاء sammy['username']‎. هذا مثال على ذلك: print(sammy['username']) # sammy-shark تتصرف القواميس كقواعد البيانات، فهي بدلًا من فهرسة العناصر بأعداد صحيحة، كما هو الحال في القوائم، فإنها تُفهرس العناصر (أو قيم القاموس) بمفاتيح، ويمكنك عبر تلك المفاتيح الحصول على القيم المقابلة لها. باستدعاء المفتاح username، سنحصل على القيمة المرتبطة به، وهي sammy-shark. وبالمِثل، يمكن استدعاء القيم الأخرى في القاموس sammy باستخدام نفس الصياغة: sammy['followers'] # 987 sammy['online'] # True استخدام التوابع للوصول إلى العناصر بالإضافة إلى استخدام المفاتيح للوصول إلى القيم، يمكننا أيضًا استخدام بعض التوابع المُضمّنة، مثل: dict.keys()‎‎: الحصول على المفاتيح dict.values()‎: الحصول على القيم dict.items()‎: الحصول على العناصر على هيئة قائمة من أزواج (key, value) لإعادة المفاتيح، نستخدم التابع dict.keys()‎، كما يوضح المثال التالي: print(sammy.keys()) # dict_keys(['followers', 'username', 'online']) تلقينا في المخرجات كائنَ عرض تكراري (iterable view object) من الصنف dict_keys يحوي المفاتيح ثم طُبِعت المفاتيح على هيئة قائمة. يمكن استخدام هذا التابع للاستعلام من القواميس. على سبيل المثال، يمكننا البحث عن المفاتيح المشتركة بين قاموسين: sammy = {'username': 'sammy-shark', 'online': True, 'followers': 987} jesse = {'username': 'JOctopus', 'online': False, 'points': 723} for common_key in sammy.keys() & jesse.keys(): print(sammy[common_key], jesse[common_key]) يحوي القاموسان sammy و jesse معلومات تعريف المستخدم. كما أنّ لهما مفاتيح مختلفة، لأنّ لدى Sammy ملف تعريف اجتماعي يضم مفتاحًا followers يمثل المتابعين على الشبكة الاجتماعية، أما Jesse فلها ملف تعريف للألعاب يضم مفتاحًا points يمثل النقاط. كلا القاموسين يشتركان في المفتاحين username و online، ويمكن العثور عليهما عند تنفيذ هذا البُريمج: # المخرجات sammy-shark JOctopus True False يمكننا بالتأكيد تحسين البرنامج لتسهيل قراءة المخرجات، ولكنّ الغرض هنا هو توضيح إمكانية استخدام dict.keys()‎ لرصد المفاتيح المشتركة بين عدة قواميس. هذا مفيد بشكل خاص عند العمل على القواميس الكبيرة. وبالمثل، يمكننا استخدام التابع dict.values()‎ للاستعلام عن القيم الموجودة في القاموس sammy على النحو التالي: sammy = {'username': 'sammy-shark', 'online': True, 'followers': 987} print(sammy.values()) # dict_values([True, 'sammy-shark', 987]) يُعيد كلا التابعان values()‎ و keys()‎‎ قوائم غير مرتبة تضم مفاتيح وقيم القاموس sammy على هيئة كائِني عرضٍ من الصنف dict_values و dict_keys على التوالي. إن أردت الحصول على الأزواج الموجودة في القاموس، فاستخدم التابع items()‎: print(sammy.items()) المخرجات ستكون: dict_items([('online', True), ('username', 'sammy-shark'), ('followers', 987)]) النتيجة المعادة ستكون على هيئة قائمة مكونة من أزواج (key, value) من الصنف dict_items. يمكننا التكرار (iterate) على القائمة المُعادة باستخدام الحلقة for. على سبيل المثال، يمكننا طباعة جميع مفاتيح وقيم القاموس المحدد، ثم جعلها أكثر مقروئية عبر إضافة سلسلة نصية توضيحية: for key, value in sammy.items(): print(key, 'is the key for the value', value) وسينتج لنا: online is the key for the value True followers is the key for the value 987 username is the key for the value sammy-shark كرَّرت الحلقة for على العناصر الموجودة في القاموس sammy، وطبعت المفاتيح والقيم سطرًا سطرًا، مع إضافة معلومات توضيحية. تعديل القواميس القواميس هي هياكل بيانات قابلة للتغيير (mutable)، أي يمكن تعديلها. في هذا القسم، سنتعلم كيفية إضافة عناصر إلى قاموس، وكيفية حذفها. إضافة وتغيير عناصر القاموس يمكنك إضافة أزواج قيمة-مفتاح إلى قاموس دون استخدام توابع أو دوال باستخدام الصياغة التالية: dict[key] = value في المثال التالي، سنضيف زوجًا مفتاح-قيمة إلى قاموس يُسمى usernames: usernames = {'Sammy': 'sammy-shark', 'Jamie': 'mantisshrimp54'} usernames['Drew'] = 'squidly' print(usernames) # {'Drew': 'squidly', 'Sammy': 'sammy-shark', 'Jamie': 'mantisshrimp54'} لاحظ أنّ القاموس قد تم تحديثه بالزوج 'Drew': 'squidly'. نظرًا لأنّ القواميس غير مرتبة، فيمكن أن يظهر الزوج المُضاف في أيّ مكان في مخرجات القاموس. إذا استخدمنا القاموس usernames لاحقًا، فسيظهر فيه الزوج المضاف حديثًا. يمكن استخدام هذه الصياغة لتعديل القيمة المرتبطة بمفتاح معيّن. في هذه الحالة، سنشير إلى مفتاح موجود سلفًا، ونمرر قيمة مختلفة إليه. سنعرِّف في المثال التالي قاموسًا باسم drew يمثِّل البيانات الخاصة بأحد المستخدمين على بعض الشبكات الاجتماعية. حصل هذا المستخدم على عدد من المتابعين الإضافيين اليوم، لذلك سنحدّث القيمة المرتبطة بالمفتاح followers ثم نستخدم التابع print()‎ للتحقق من أنّ القاموس قد عُدِّل. drew = {'username': 'squidly', 'online': True, 'followers': 305} drew['followers'] = 342 print(drew) # {'username': 'squidly', 'followers': 342, 'online': True} في المخرجات نرى أنّ عدد المتابعين قد قفز من 305 إلى 342. يمكننا استخدام هذه الطريقة لإضافة أزواج قيمة-مفتاح إلى القواميس عبر مدخلات المستخدم. سنكتب بريمجًا سريعًا، usernames.py، يعمل من سطر الأوامر ويسمح للمستخدم بإضافة الأسماء وأسماء المستخدمين المرتبطة بها: # تعريف القاموس الأصلي usernames = {'Sammy': 'sammy-shark', 'Jamie': 'mantisshrimp54'} # while إعداد الحلقة التكرارية while True: # اطلب من المستخدم إدخال اسم print('Enter a name:') # name تعيين المدخلات إلى المتغير name = input() # تحقق مما إذا كان الاسم موجودًا في القاموس ثم اطبع الرد if name in usernames: print(usernames[name] + ' is the username of ' + name) # إذا لم يكن الاسم في القاموس else: # اطبع الرد print('I don\'t have ' + name + '\'s username, what is it?') # خذ اسم مستخدم جديد لربطه بذلك الاسم username = input() # name عين قيمة اسم المستخدم إلى المفتاح usernames[name] = username # اطبع ردًا يبيّن أنّ البيانات قد حُدّثت print('Data updated.') سننفِّذ البرنامج من سطر الأوامر: python usernames.py عندما ننفّذ البرنامج، سنحصل على مخرجات مشابهة لما يلي: Enter a name: Sammy sammy-shark is the username of Sammy Enter a name: Jesse I don't have Jesse's username, what is it? JOctopus Data updated. Enter a name: عند الانتهاء من اختبار البرنامج، اضغط على CTRL + C للخروج من البرنامج. يمكنك تخصيص حرف لإنهاء البرنامج (مثل الحرف q)، وجعل البرنامج يُنصت له عبر العبارات الشرطية. يوضح هذا المثال كيف يمكنك تعديل القواميس بشكل تفاعلي. في هذا البرنامج، بمجرد خروجك باستخدام CTRL + C، ستفقد جميع بياناتك، إلا إن خزّنت البيانات في ملف. يمكننا أيضًا إضافة عناصر إلى القواميس وتعديلها باستخدام التابع dict.update()‎. هذا التابع مختلف عن التابع append()‎ الذي يُستخدم مع القوائم. سنضيف المفتاح followers في القاموس jesse أدناه، ونَمنحه قيمة عددية صحيحة بواسطة التابع jesse.update()‎. بعد ذلك، سنطبع القاموس المُحدّث. jesse = {'username': 'JOctopus', 'online': False, 'points': 723} jesse.update({'followers': 481}) print(jesse) # {'followers': 481, 'username': 'JOctopus', 'points': 723, 'online': False} من المخرجات، نتبيّن أننا نجحنا في إضافة الزوج ‎followers: 481 إلى القاموس jesse. يمكننا أيضًا استخدام التابع dict.update()‎ لتعديل زوج قيمة-مفتاح موجود سلفًا عن طريق استبدال قيمة مفتاح معيَّن. سنغيِّر القيمة المرتبطة بالمفتاح online في القاموس Sammy من True إلى False: sammy = {'username': 'sammy-shark', 'online': True, 'followers': 987} sammy.update({'online': False}) print(sammy) # {'username': 'sammy-shark', 'followers': 987, 'online': False} يغيّر السطر sammy.update({'online': False})‎ القيمة المرتبطة بالمفتاح 'online' من True إلى False. عند استدعاء التابع print()‎‎ على القاموس، يمكنك أن ترى في المخرجات أنّ التحديث قد تمّ. لإضافة عناصر إلى القواميس أو تعديل القيم، يمكن إمّا استخدام الصياغة dict[key] = value، أو التابع dict.update()‎. حذف عناصر من القاموس كما يمكنك إضافة أزواج قيمة-مفتاح إلى القاموس، أو تغيير قيمه، يمكنك أيضًا حذف العناصر الموجودة في القاموس. لتزيل زوج قيمة-مفتاح من القاموس، استخدم الصياغة التالية: del dict[key] لنأخذ القاموس jesse الذي يمثل أحد المستخدمين، ولنفترض أنّ jesse لم تعد تستخدم المنصة لأجل ممارسة الألعاب، لذلك سنزيل العنصر المرتبط بالمفتاح points. بعد ذلك، سنطبع القاموس لتأكيد حذف العنصر: jesse = {'username': 'JOctopus', 'online': False, 'points': 723, 'followers': 481} del jesse['points'] print(jesse) # {'online': False, 'username': 'JOctopus', 'followers': 481} يزيل السطر del jesse ['points']‎ الزوج 'points': 723 من القاموس jesse. إذا أردت محو جميع عناصر القاموس، فيمكنك ذلك باستخدام التابع dict.clear()‎. سيَبقى هذا القاموس في الذاكرة، وهذا مفيد في حال احتجنا إلى استخدامه لاحقًا في البرنامج، بيْد أنه سيُفرِّغ جميع العناصر من القاموس. دعنا نزيل كل عناصر القاموس jesse: jesse = {'username': 'JOctopus', 'online': False, 'points': 723, 'followers': 481} jesse.clear() print(jesse) # {} تُظهِر المخرجات أنّ القاموسًا صار فارغًا الآن. إذا لم تعد بحاجة إلى القاموس، فاستخدم del للتخلص منه بالكامل: del jesse print(jesse) إذا نفّذت الأمر print()‎ بعد حذف القاموس jesse، سوف تتلقى الخطأ التالي: NameError: name 'jesse' is not defined خلاصة ألقينا في هذه المقالة نظرة على القواميس في بايثون. تتألف القواميس من أزواج قيمة-مفتاح، وتوفر حلًّا ممتازًا لتخزين البيانات دون الحاجة إلى فهرستها. يتيح لنا ذلك استرداد القيم بناءً على معانيها وعلاقتها بأنواع البيانات الأخرى. يمكنك تعلم المزيد عن أنواع البيانات الأخرى من المقالة التالية: "فهم أنواع البيانات في بايثون 3". هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Dictionaries in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية استيراد الوحدات في بايثون 3 المقالة السابقة: فهم نوع البيانات Tuples في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  13. يتضمّن بايثون عددًا من هياكل البيانات، بما في ذلك القوائم. تمكّن هياكل البيانات من تنظيم البيانات وتخزينها عبر توابع خاصة مُضمّنة في بايثون. للاستفادة من محتويات هذه المقالة، ينبغي أن تكون على إلمام بأساسيات القوائم، وصياغتها وكيفية فهرستها. يمكنك تعلم ذلك من المقالة السابقة: القوائم في بايثون 3. سنتعرف على التوابع المُضمنة التي يمكن استخدامها مع القوائم. ونتعلم كيفية إضافة عناصر إلى القائمة وكيفية إزالتها، وتوسيع القوائم وترتيبها، وغير ذلك. القوائم أنواعٌ قابلةٌ للتغيير (mutable) على عكس السلاسل النصية التي لا يمكن تغييرها، فعندما تستخدم تابعًا على قائمة ما، ستؤثر في القائمة نفسها، وليس في نسخة منها. سنعمل في هذه المقالة على قائمة تمثل حوض سمك، إذ ستحوي القائمة أسماء أنواع الأسماك الموجودة في الحوض، وسنعدلها كلما أضفنا أسماكًا أو أزلناها من الحوض. التابع list.append()‎‎ يضيف التابع list.append(x)‎ عنصرًا (x) إلى نهاية القائمة. يُعرّف المثال التالي قائمةً تمثل الأسماك الموجودة في حوض السمك. fish = ['barracuda','cod','devil ray','eel'] تتألف هذه القائمة من 4 سلاسل نصية، وتتراوح فهارسها من 0 إلى 3. سنضيف سمكة جديدة إلى الحوض، ونود بالمقابل أن نضيف تلك السمكة إلى قائمتنا. سنمرّر السلسلة النصية flounder التي تمثل نوع السمكة الجديدة إلى التابع list.append()‎، ثم نطبع قائمتنا المعدلة لتأكيد إضافة العنصر. fish.append('flounder') print(fish) # ['barracuda', 'cod', 'devil ray', 'eel', 'flounder'] الآن، صارت لدينا قائمة من 5 عناصر، تنتهي بالعنصر الذي أضفناه للتو عبر التابع append()‎. التابع list.insert()‎ يأخذ التابع list.insert (i,x)‎ وسيطين: الأول i يمثِّل الفهرس الذي ترغب في إضافة العنصر عنده، و x يمثل العنصر نفسه. لقد أضفنا إلى حوض السمك سمكة جديدة من نوع anchovy. ربما لاحظت أنّ قائمة الأسماك مرتبة ترتيبًا أبجديًا حتى الآن. لهذا السبب لا نريد إفساد الترتيب، لذا لن نضيف السلسلة النصية anchovy إلى نهاية القائمة باستخدام الدالة list.append()‎. بدلًا من ذلك، سنستخدم التابع list.insert()‎ لإضافة anchovy إلى بداية القائمة، أي عند الفهرس 0: fish.insert(0,'anchovy') print(fish) # ['anchovy', 'barracuda', 'cod', 'devil ray', 'eel', 'flounder'] في هذه الحالة، أضفنا العنصر إلى بداية القائمة. ستتقدم فهارس العناصر التالية خطوةً واحدةً إلى الأمام. لذلك، سيصبح العنصر barracuda عند الفهرس 1، والعنصر cod عند الفهرس 2، والعنصر flounder - الأخير - عند الفهرس 5. سنحضر الآن سمكة من نوع damselfish إلى الحوض، ونرغب في الحفاظ على الترتيب الأبجدي لعناصر القائمة أعلاه، لذلك سنضع هذا العنصر عند الفهرس 3: fish.insert(3,'damselfish')‎. التابع list.extend()‎ إذا أردت أن توسّع قائمة بعناصر قائمة أخرى، فيمكنك استخدام التابع list.extend(L)‎، والذي يأخذ قائمة كمعامل. سنضع في الحوض أربعة أسماك جديدة. أنواع هذه الأسماك مجموعة معًا في القائمة more_fish: more_fish = ['goby','herring','ide','kissing gourami'] سنضيف الآن عناصر القائمة more_fish إلى قائمة الأسماك، ونطبع القائمة لنتأكد من أنّ عناصر القائمة الثانية قد ضُمّت إليها: fish.extend(more_fish) print(fish) ستطبع بايثون القائمة التالية: ['anchovy', 'barracuda', 'cod', 'devil ray', 'eel', 'flounder', 'goby', 'herring', 'ide', 'kissing gourami'] في هذه المرحلة، صارت القائمة fish تتألف من 10 عناصر. التابع list.remove()‎ لإزالة عنصر من قائمة، استخدم التابع list.remove(x)‎، والذي يزيل أول عنصر من القائمة له القيمة المُمرّرة x. جاءت مجموعة من العلماء المحليين لزيارة الحوض. سيجرون أبحاثًا عن النوع kissing gourami، وطلبوا استعارة السمكة kissing gourami، لذلك نود إزالة العنصر kissing gourami من القائمة لنعكس هذا التغيير: fish.remove('kissing gourami') print(fish) والمخرجات ستكون: ['anchovy', 'barracuda', 'cod', 'devil ray', 'eel', 'flounder', 'goby', 'herring', 'ide'] بعد استخدام التابع list.remove()‎، لم يعد العنصر kissing gourami موجودًا في القائمة. في حال مرّرت عنصرًا x غير موجود في القائمة إلى التابع list.remove()‎، فسيُطلق الخطأ التالي: ValueError: list.remove(x): x not in list التابع list.remove()‎ لن يزيل إلا أول عنصر تساوي قيمته قيمة العنصر المُمرّر إلى التابع، لذلك إن كانت لدينا سمكتان من النوع kissing gourami في الحوض، وأعرنا إحداهما فقط للعلماء، فإنّ التعبير fish.remove('kissing gourami')‎ لن يمحو إلا العنصر الأول المطابق فقط. التابع list.pop()‎ يعيد التابع list.pop ()‎ العنصر الموجود عند الفهرس المحدد من القائمة، ثم يزيل ذلك العنصر. تشير الأقواس المربعة حول i إلى أنّ هذا المعامل اختياري، لذا، إذا لم تحدد فهرسًا (كما في fish.pop()‎)، فسيُعاد العنصر الأخير ثم يُزال. لقد أصبح حجم السمكة devil ray كبيرًا جدًا، ولم يعد الحوض يسعها، ولحسن الحظ أنّ هناك حوض سمك في بلدة مجاورة يمكنه استيعابها. سنستخدم التابع ‎.‎pop()‎، ونمرر إليه العدد 3، الذي يساوي فهرس العنصر devil ray، بقصد إزالته من القائمة. بعد إعادة العنصر، سنتأكد من أننا أزلنا العنصر الصحيح. print(fish.pop(3)) # devil ray print(fish) # ['anchovy', 'barracuda', 'cod', 'eel', 'flounder', 'goby', 'herring', 'ide'] باستخدام التابع ‎.pop()‎ تمكّنا من إعادة وإزالة ray devil من قائمة الأسماك. إذا لم نمرر أيّ معامل إلى هذا التابع، ونفّذنا التعبير fish.pop()‎، فسيُعاد العنصر الأخير ide ثم يُزَال من القائمة. التابع list.index()‎ يصعب في القوائم الكبيرة تحديد فهارس العناصر التي تحمل قيمة معينة. لأجل ذلك، يمكننا استخدام التابع list.index(x)‎، حيث يمثل الوسيطx قيمة العنصر المبحوث عنه، والذي نريد إعادة فهرسه. إذا كان هناك أكثر من عنصر واحد يحمل القيمة x، فسيُعَاد فهرس العنصر الأول. print(fish) # ['anchovy', 'barracuda', 'cod', 'eel', 'flounder', 'goby', 'herring', 'ide'] print(fish.index('herring')) # 6 سوف يُطلق خطأ في حال مرّرنا قيمة غير موجودة في القائمة إلى التابع ‎.index()‎. التابع list.copy()‎ أحيانُا نرغب في تعديل عناصر قائمةٍ والتجريب عليها، مع الحفاظ على القائمة الأصلية دون تغيير؛ يمكننا في هذه الحالة استخدام التابع list.copy()‎ لإنشاء نسخة من القائمة الأصلية. في المثال التالي، سنمرّر القيمة المعادة من fish.copy()‎ إلى المتغير fish_2، ثم نطبع قيمة fish_2 للتأكد من أنها تحتوي على نفس عناصر القائمة fish. fish_2 = fish.copy() print(fish_2) # ['anchovy', 'barracuda', 'cod', 'eel', 'flounder', 'goby', 'herring', 'ide'] في هذه المرحلة، القائمتان fish و fish_2 متساويتان. التابع list.reverse()‎ يمكننا عكس ترتيب عناصر قائمة باستخدام التابع list.reverse()‎. في المثال التالي سنستخدم التابع ‎.reverse()‎ مع القائمة fish لعكس ترتيب عناصرها. fish.reverse() print(fish) # ['ide', 'herring', 'goby', 'flounder', 'eel', 'cod', 'barracuda', 'anchovy'] بعد استخدام التابع ‎.reverse()‎، صارت القائمة تبدأ بالعنصر ide، والذي كان في نهاية القائمة من قبل، كما ستنتهي القائمة بالعنصر anchovy، والذي كان في بداية القائمة من قبل. التابع list.count()‎ يعيد التابع list.count(x)‎ عدد مرات ظهور القيمة x في القائمة. هذا التابع مفيد في حال كنا نعمل على قائمة طويلة بها الكثير من القيم المتطابقة. إذا كان حوض السمك كبيرًا، على سبيل المثال، وكانت عندنا عدة أسماك من النوع neon tetra، فيمكننا استخدام التابع ‎.count()‎ لتحديد العدد الإجمالي لأسماك هذا النوع. في هذا المثال سنحسب عدد مرات ظهور العنصر goby: print(fish.count('goby')) # 1 تظهر السلسلة النصية goby مرةً واحدةً فقط في القائمة، لذا سيُعيد التابع ‎.count()‎ العدد 1. يمكننا استخدام هذا التابع أيضًا مع قائمة مكوَّنة من أعداد صحيحة. المثال التالي يوضح ذلك. يتتبع المشرفون على الحوض أعمار الأسماك الموجودة فيه للتأكد من أنّ وجباتها الغذائية مناسبة لأعمارها. هذه القائمة الثانية المُسماة fish_ages تتوافق مع أنواع السمك في القائمة fish. نظرًا لأنّ الأسماك التي لا يتجاوز عمرها عامًا واحدًا لها احتياجات غذائية خاصة، فسنحسب عدد الأسماك التي عمرها عامًا واحدًا: fish_ages = [1,2,4,3,2,1,1,2] print(fish_ages.count(1)) # 3 يظهر العدد الصحيح 1 في القائمة fish_ages ثلاث مرات، لذلك يعيد التابع ‎.count()‎ العدد 3. التابع list.sort()‎ يُستخدم التابع list.sort()‎ لترتيب عناصر القائمة التي استُدعِي معها . سنستخدم قائمة الأعداد الصحيحة fish_ages لتجريب التابع ‎.sort()‎: fish_ages.sort() print(fish_ages) # [1, 1, 1, 2, 2, 2, 3, 4] باستدعاء التابع ‎.sort()‎ مع القائمة fish_ages، ستُعاد قائمة الأعداد الصحيحة مرتبةً. التابع list.clear()‎ بعد الانتهاء من العمل على قائمة ما، يمكنك إزالة جميع القيم الموجودة فيها باستخدام التابع list.clear()‎. قررت الحكومة المحلية الاستيلاء على حوض السمك الخاص بنا، وجعله مساحة عامة يستمتع بها سكان مدينتنا. نظرًا لأننا لم نعد نعمل على الحوض، فلم نعد بحاجة إلى الاحتفاظ بقائمة الأسماك، لذلك سنزيل عناصر القائمة fish: fish.clear() print(fish) # [] نرى في المخرجات أقواسًا معقوفة نتيجة استدعاء التابع ‎.clear()‎ على القائمة fish، وهذا تأكيد على أنّ القائمة أصبحت خالية من جميع العناصر. خلاصة لمَّا كانت القوائم تسلسلات قابلة للتغيير (mutable)، فإنّها هياكلُ بيانات مرنة ومفيدة للغاية. كما تتيح لنا توابع القوائم إجراء العديد من العمليات على القوائم بسهولة، إذ يمكننا استخدام التوابع لتعديل القوائم وترتيبها ومعالجتها بفعالية. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Use List Methods in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: فهم كيفية استعمال List Comprehensions في بايثون 3 المقالة السابقة: مدخل إلى القوائم في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  14. القائمة هي بنية بيانات في بايثون، وهي عبارة عن تسلسل مُرتّب من العناصر قابل للتغيير (mutable). table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } القوائم مناسبة لتجميع العناصر المترابطة، إذ تمكّنك من تجميع البيانات المتشابهة، أو التي تخدم غرضًا معيّنا معًا، وتكثيف الشيفرة البرمجية، وتنفيذ التوابع والعمليات على عدة قيم في وقت واحد. القوائم في بايثون وغيرها من هياكل البيانات المركبة تساعد على تمثيل المجموعات، مثل مجموعة الملفات في مجلد على حاسوبك، أو قوائم التشغيل، ورسائل البريد الإلكتروني، وغير ذلك. في المثال التالي، سننشئ قائمةً تحتوي على عناصر من نوع السلاسل النصية: sea_creatures = ['shark', 'cuttlefish', 'squid', 'mantis shrimp', 'anemone'] عندما نطبع القائمة، فإنّ المخرجات ستشبه القائمة التي أنشأناها: print(sea_creatures) # ['shark', 'cuttlefish', 'squid', 'mantis shrimp', 'anemone'] باعتبارها تسلسلًا مرتبًا من العناصر، يمكن استدعاء أيّ عنصر من القائمة عبر الفهرسة. القوائم هي بيانات مركبة تتألف من أجزاء أصغر، وتتميز بالمرونة، إذ يمكن إضافة عناصر إليها، وإزالتها، وتغييرها. عندما تحتاج إلى تخزين الكثير من القيم، أو التكرار (iterate) عليها، وتريد أن تكون قادرًا على تعديل تلك القيم بسهولة، فالقوائم هي خيارك الأفضل. سنتعرف في هذه المقالة على القوائم وتوابعها وكيفية استخدامها. فهرسة القوائم (Indexing Lists) كل عنصر في القائمة يقابله رقم يمثل فهرسَ ذلك العنصر، والذي هو عدد صحيح، فهرس العنصر الأول في القائمة هو 0. هذا تمثيل لفهارس القائمة sea_creatures: shark cuttlefish squid shrimp anemone 0 1 2 3 4 يبدأ العنصر الأول، أي السلسلة النصية shark، عند الفهرس 0، وتنتهي القائمة عند الفهرس 4 الذي يقابل العنصر anemone. نظرًا لأنّ كل عنصر في قوائم بايثون يقابله رقم فهرس، يمكننا الوصول إلى عناصر القوائم ومعالجتها كما نفعل مع أنواع البيانات المتسلسلة الأخرى. يمكننا الآن استدعاء عنصر من القائمة من خلال رقم فهرسه: print(sea_creatures[1]) # cuttlefish تتراوح أرقام الفهارس في هذه القائمة بين 0 و 4، كما هو موضح في الجدول أعلاه. المثال التالي يوضح ذلك: sea_creatures[0] = 'shark' sea_creatures[1] = 'cuttlefish' sea_creatures[2] = 'squid' sea_creatures[3] = 'mantis shrimp' sea_creatures[4] = 'anemone' إذا استدعينا القائمة sea_creatures برقم فهرس أكبر من 4، فسيكون الفهرس خارج النطاق، وسيُطلَق الخطأ IndexError: print(sea_creatures[18]) والمخرجات ستكون: IndexError: list index out of range يمكننا أيضًا الوصول إلى عناصر القائمة بفهارس سالبة، والتي تُحسَب من نهاية القائمة، بدءًا من ‎-1. هذا مفيد في حال كانت لدينا قائمة طويلة، وأردنا تحديد عنصر في نهايته. بالنسبة لقائمة sea_creatures، تبدو الفهارس السالبة كما يلي: shark cuttlefish squid shrimp anemone -5 -4 -3 -2 -1 في المثال التالي، سنطبع العنصر squid باستخدام فهرس سالب: print(sea_creatures[-3]) # squid يمكننا ضم (concatenate) عنصر يحتوي سلسلة نصية مع سلسلة نصبة أخرى باستخدام العامل +: print('Sammy is a ' + sea_creatures[0]) # Sammy is a shark لقد ضممنا السلسلة النصية Sammy is a مع العنصر ذي الفهرس 0. يمكننا أيضًا استخدام العامل + لضمّ قائمتين أو أكثر معًا (انظر الفقرة أدناه): تساعدنا الفهارس على الوصول إلى أيّ عنصر من عناصر القائمة والعمل عليه. تعديل عناصر القائمة يمكننا استخدام الفهرسة لتغيير عناصر القائمة، عن طريق إسناد قيمة إلى عُنصر مُفهرس من القائمة. هذا يجعل القوائم أكثر مرونة، ويسهّل تعديل وتحديث عناصرها. إذا أردنا تغيير قيمة السلسلة النصية للعنصر الموجود عند الفهرس 1، من القيمة cuttlefish إلى octopus، فيمكننا القيام بذلك على النحو التالي: sea_creatures[1] = 'octopus' الآن، عندما نطبع sea_creatures، ستكون النتيجة: print(sea_creatures) # ['shark', 'octopus', 'squid', 'mantis shrimp', 'anemone'] يمكننا أيضًا تغيير قيمة عنصر باستخدام فهرس سالب: sea_creatures[-3] = 'blobfish' print(sea_creatures) # ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone'] الآن استبدلنا بالسلسلة squid السلسلة النصية blobfish الموجودة عند الفهرس السالب ‎-3 (والذي يتوافق مع الفهرس الموجب 2). اقتطاع القوائم (Slicing Lists) يمكننا أيضا استدعاء عدة عناصر من القائمة. لنفترض أننا نرغب في طباعة العناصر الموجودة في وسط القائمة sea_creatures، يمكننا القيام بذلك عن طريق اقتطاع شريحة (جزء) من القائمة. يمكننا باستخدام الشرائح استدعاء عدة قيم عن طريق إنشاء مجال من الفهارس مفصولة بنقطتين [x: y]: print(sea_creatures[1:4]) # ['octopus', 'blobfish', 'mantis shrimp'] عند إنشاء شريحة، كما في [‎1: 4]، يبدأ الاقتطاع من العنصر ذي الفهرس الأول (مشمولًا)، وينتهي عند العنصر ذي الفهرس الثاني (غير مشمول)، لهذا طُبعَت في مثالنا أعلاه العناصر الموجودة في المواضع، 1، و 2، و 3. إذا أردنا تضمين أحد طرفي القائمة، فيمكننا حذف أحد الفهرسَين في التعبير [x: y]. على سبيل المثال، إذا أردنا طباعة العناصر الثلاثة الأولى من القائمة sea_creatures، يمكننا فعل ذلك عن طريق كتابة: print(sea_creatures[:3]) # ['shark', 'octopus', 'blobfish'] لقد تم طبع العناصر من بداية القائمة حتّى العنصرٍ ذي الفهرس 3. لتضمين جميع العناصر الموجودة في نهاية القائمة، سنعكس الصياغة: print(sea_creatures[2:]) # ['blobfish', 'mantis shrimp', 'anemone'] يمكننا أيضًا استخدام الفهارس السالبة عند اقتطاع القوائم، تمامًا كما هو الحال مع الفهارس الموجبة: print(sea_creatures[-4:-2]) # ['octopus', 'blobfish'] print(sea_creatures[-3:]) # ['blobfish', 'mantis shrimp', 'anemone'] هناك معامل آخر يمكننا استخدامه في الاقتطاع، ويشير إلى عدد العناصر التي يجب أن يتم القفز عليها (أو الخطوة) بعد استرداد العنصر الأول من القائمة. حتى الآن، لقد أغفلنا المعامل stride، وستعطيه بايثون القيمة الافتراضية 1، ما يعني أنه سيتم استرداد كل العناصر الموجودة بين الفهرسَين المحددين. ستكون لصياغة على الشكل التالي [x: y: z]، إذ يشير z إلى الخطوة (stride). في المثال التالي، سننشئ قائمة كبيرة، ثم نقتطعها، مع خطوة اقتطاع تساوي 2: numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] print(numbers[1:11:2]) # [1, 3, 5, 7, 9] سيطبع التعبير numbers[‎1: 11: 2]‎ القيم ذات الفهارس المحصورة بين 1 (مشمولة) و 11 (غير مشمولة)، وسيقفز البرنامج بخطوتين كل مرة، ويطبع العناصر المقابلة. يمكننا حذف المُعاملين الأوّليين، واستخدام الخطوة وحدها كمعامل وفق الصياغة التالية: [‎::z]: print(numbers[::3]) # [0, 3, 6, 9, 12] عند طباعة القائمة ‎numbers مع تعيين الخطوة عند القيمة 3، فلن تُطبع إلا العناصر التي فهارسها من مضاعفات 3: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 يجعل استخدام الفهارس الموجبة والسالبة ومعامل الخطوة في اقتطاع القوائم التحكم في القوائم ومعالجتها أسهل وأكثر مرونة. تعديل القوائم بالعوامل يمكن استخدام العوامل لإجراء تعديلات على القوائم. سننظر في استخدام العاملين + و * ومقابليهما المركبين ‎+=‎ و ‎*=‎‎. يمكن استخدام العامل + لضمّ (concatenate) قائمتين أو أكثر معًا: sea_creatures = ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone'] oceans = ['Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic'] print(sea_creatures + oceans) والمخرجات هي: ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic'] يمكن استخدام العامل + لإضافة عنصر (أو عدة عناصر) إلى نهاية القائمة. لكن تذكر أن تضع العنصر بين قوسين مربعين: sea_creatures = sea_creatures + ['yeti crab'] print (sea_creatures) # ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] يمكن استخدام العامل * لمضاعفة القوائم (multiply lists). ربما تحتاج إلى عمل نُسِخٍ لجميع الملفات الموجودة في مجلد على خادم، أو مشاركة قائمة أفلام مع الأصدقاء؛ ستحتاج في هذه الحالات إلى مضاعفة مجموعات البيانات. سنضاعف القائمة sea_creatures مرتين، والقائمة oceans ثلاث مرات: print(sea_creatures * 2) print(oceans * 3) والنتيجة ستكون: ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab', 'shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] ['Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic', 'Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic', 'Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic'] يمكننا باستخدام العامل * نسخ القوائم عدة مرات. يمكننا أيضًا استخدام الشكلين المركبين للعاملين + و * مع عامل الإسناد =. يمكن استخدام العاملين المركبين ‎+=‎ و ‎*=‎ لملء القوائم بطريقة سريعة ومُؤتمتة. يمكنك استخدام هذين العاملين لملء القوائم بعناصر نائبة (placeholders) يمكنك تعديلها في وقت لاحق بالمدخلات المقدمة من المستخدم على سبيل المثال. في المثال التالي، سنضيف عنصرًا إلى القائمة sea_creatures. سيعمل هذا العنصر مثل عمل العنصر النائب، ونود إضافة هذا العنصر النائب عدة مرات. لفعل ذلك، سنستخدم ‎+=‎ العامل مع الحلقة for. for x in range(1,4): sea_creatures += ['fish'] print(sea_creatures) والمخرجات ستكون: ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab', 'fish'] ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab', 'fish', 'fish'] ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab', 'fish', 'fish', 'fish'] سيُضاف لكل تكرار في الحلقة for عنصر fish إلى القائمة sea_creatures. العامل ‎*=‎ يتصرف بطريقة مماثلة: sharks = ['shark'] for x in range(1,4): sharks *= 2 print(sharks) الناتج سيكون: ['shark', 'shark'] ['shark', 'shark', 'shark', 'shark'] ['shark', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark'] إزالة عنصر من قائمة يمكن إزالة العناصر من القوائم باستخدام التعبير del. سيؤدي ذلك إلى حذف العنصر الموجود عند الفهرس المحدد. سنزيل من القائمة sea_creatures العنصر octopus. هذا العنصر موجود عند الفهرس 1. لإزالة هذا العنصر، سنستخدم العبارة del ثم نستدعي متغير القائمة وفهرس ذلك العنصر: sea_creatures =['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] del sea_creatures[1] print(sea_creatures) # ['shark', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] الآن، العنصر الموجود عند الفهرس 1، أي السلسلة النصية octopus، لم يعد موجودًا في قائمتنا. يمكننا أيضًا تحديد مجال مع العبارة del. لنقل أننا نريد إزالة العناصر octopus، و blobfish و mantis shrimp معًا. يمكننا فعل ذلك على النحو التالي: sea_creatures =['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] del sea_creatures[1:4] print(sea_creatures) # ['shark', 'anemone', 'yeti crab'] باستخدام مجال مع العبارة del، تمكنا من إزالة العناصر الموجودة بين الفهرسَين 1 (مشمول) و 4 (غير مشمول)، والقائمة أضحت مكوّنة من 3 عناصر فقط بعد إزالة 3 عناصر منها. بناء قوائم من قوائم أخرى موجودة يمكن أن تتضمّن عناصر القوائم قوائم أخرى، مع إدراج كل قائمة بين قوسين معقوفين داخل الأقواس المعقوفة الخارجية التابعة لقائمة الأصلية: sea_names = [['shark', 'octopus', 'squid', 'mantis shrimp'],['Sammy', 'Jesse', 'Drew', 'Jamie']] تسمى القوائم المُتضمّنة داخل قوائم أخرى بالقوائم المتشعبة (nested lists). للوصول إلى عنصر ضمن هذه القائمة، سيتعيّن علينا استخدام فهارس متعددة: print(sea_names[1][0]) # Sammy print(sea_names[0][0]) # shark فهرس القائمة الأولى يساوي 0، والقائمة الثانية فهرسُها 1. ضمن كل قائمة متشعبة داخلية، سيكون هناك فهارس منفصلة، والتي سنسميها فهارس ثانوية: sea_names[0][0] = 'shark' sea_names[0][1] = 'octopus' sea_names[0][2] = 'squid' sea_names[0][3] = 'mantis shrimp' sea_names[1][0] = 'Sammy' sea_names[1][1] = 'Jesse' sea_names[1][2] = 'Drew' sea_names[1][3] = 'Jamie' عند العمل مع قوائم مؤلّفة من قوائم، من المهم أن تعي أنك ستحتاج إلى استخدام أكثر من فهرس واحد للوصول إلى عناصر القوائم المتشعبة. خلاصة القوائم هي نوع بيانات مرن يمكن تعديله بسهولة خلال أطوار البرنامج. غطينا في هذه المقالة الميزات والخصائص الأساسية لقوائم، بما في ذلك الفهرسة والاقتطاع والتعديل والضّم. يمكنك تعلم المزيد عن القوائم من هاتين المقالتين: كيفية استخدام توابع القوائم في بايثون 3 فهم كيفية استعمال List Comprehensions في بايثون 3 هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Lists in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية استخدام توابع القوائم في بايثون 3 المقالة السابقة: فهم العمليات المنطقية في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون
  15. Version 1.0.0

    1,289 تحميلات

    لا يخفى على أي مطور ويب خصوصًا مطوري واجهة المستخدم الأمامية (front end developers) أهمية إضفاء بعض الحيوية على المواقع التي يصممونها عبر الحركات، إذ هنالك عدة فوائد يمكن تحصليها من إضافة الحركات إلى صفحات الموقع أهمها الابتعاد عن السكون الممل، ولفت الانتباه، وتحسين التواصل مع الزائر أو المستخدم وغيرها؛ لذلك، أصبحت الحركات أمرًا جوهريًا في مواقع الويب لا يمكن الاستغناء عنها. جاء هذا الكتاب لشرح مفهوم الحركة وكيفية تحريك العناصر باستخدام CSS فقط بدءًا من الحركات البسيطة وحتى الحركات المعقَّدة المُتقدِّمة بالإضافة إلى التَطرُّق إلى مناقشة مسألة متى يجب إضافة الحركات ومتى يجب الابتعاد عنها. هذا الكتاب مترجم عن كتاب «CSS Animation 101» لصاحبه Donovan Hutchinson، ونأمل أن يكون إضافةً نافعةً للمكتبة العربيَّة وأن يفيد القارئ العربي في الإلمام بموضوع التحريك عبر CSS. هذا الكتاب مرخص بموجب رخصة المشاع الإبداعي Creative Commons «نسب المُصنَّف - غير تجاري - الترخيص بالمثل 4.0».