اذهب إلى المحتوى

البحث في الموقع

المحتوى عن 'أتمتة المهام ببايثون'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

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

  • بداية

    نهاية


المجموعة


النبذة الشخصية

تم العثور على 8 نتائج

  1. من المرجح أنك تعرف كيف تبحث عن النصوص بالضغط على Ctrl+f وإدخال الكلمات التي تريد البحث عنها، في حين أن التعابير النمطية Regular expressions تنقل الأمور إلى مرحلة أعلى: فهي تسمح لك بتحديد «نمط» النص الذي تبحث عنه، فلو لم تكن تعرف رقم هاتف الشركة في الورقة أمامك، لكنك تعيش في الولايات المتحدة أو كندا، فستعلم أن أرقام الهاتف تتألف من 3 أرقام ثم شرطة -، ثم 4 أرقام أخرى (وقد يبدأ برمز المنطقة وهو 3 أرقام في البداية). أي أنك ستعرف أن ما يلي هو رقم هاتف 415‎-555-1234 لكن 4,155,551,234 ليس رقمًا هاتفيًا. يمكننا التعرف على مختلف أنماط النصوص بسهولة: فعناوين البريد الإلكتروني تحتوي الرمز @ في منتصفها، وعناوين URL للمواقع تحتوي على نقط وخطوط مائلة، والعناوين الأخبارية تستعمل نسق العنوان Title case، والتاغات في وسائل التواصل الاجتماعي تبدأ برمز # ولا تحتوي على فراغات …إلخ. التعابير النمطية مفيدة جدًا، لكن قلّة من غير المبرمجين يعرفونها على الرغم من أن أغلبية المحررات النصية الحديثة ومعالجات النصوص مثل مايكرروسوفت وورد وليبرأوفيس رايتر يمتلكون خاصيات بحث واستبدال تستعمل التعابير النمطية. والتعابير النمطية تسرع معالجة النصوص لدرجة كبيرة، فلا يستفيد منها مستخدمو برمجيات التحرير فقط، بل المبرمجون أيضًا، ويقول الكاتب الشهير كوري دكتورو أن علينا تدريس التعابير النمطية قبل تدريس البرمجة: سنبدأ هذا المقال بكتابة برنامج لمطابقة نمط من النصوص دون استخدام التعابير النمطية، ثم سنرى كيف يمكننا كتابة شيفرة بسيطة باستخدام التعابير النمطية؛ وسنبدأ بالتعابير البسيطة ثم ننتقل إلى الميزات المتقدمة أكثر، مثل استبدال النصوص وفئات الحروف، ثم سنكتب في نهاية المقال مشروعًا بسيطًا يستخرج أرقام الهواتف وعناوين البريد الإلكتروني من مستند نصي. مطابقة الأنماط دون استخدام التعابير النمطية لنقل أننا نريد البحث عن رقم هاتف يتبع نظام الكتابة الأمريكي في سلسلة نصية، أي يحتوي 3 أرقام ثم شرطة ثم 3 أرقام ثم شرطة ثم 4 أرقام، مثل ‎415-555-4242. لنكتب دالةً باسم isPhoneNumber()‎ للتحقق إن كانت تمثل السلسلة النصية رقم هاتف، وتعيد القيمة True أو False. احفظ الشيفرة الآتية في ملف باسم isPhoneNumber.py: def isPhoneNumber(text): ➊ if len(text) != 12: return False for i in range(0, 3): ➋ if not text[i].isdecimal(): return False ➌ if text[3] != '-': return False for i in range(4, 7): ➍ if not text[i].isdecimal(): return False ➎ if text[7] != '-': return False for i in range(8, 12): ➏ if not text[i].isdecimal(): return False ➐ return True print('Is 415-555-4242 a phone number?') print(isPhoneNumber('415-555-4242')) print('Is ABC a phone number?') print(isPhoneNumber(ABC)) حين تجربة المثال السابق فسينتج ما يلي: Is 415-555-4242 a phone number? True Is ABC a phone number? False تحتوي الدالة isPhoneNumber()‎ على شيفرة تجري عدة عمليات تحقق للتأكد أن السلسلة النصية في المعامل text هي رقم هاتف صالح، وإذا فشلت أي تحقق من هذه التحققات فستعيد الدالة القيمة False. تبدأ الشيفرة بالتحقق أن السلسلة النصية بطول 12 محرفًا تمامًا ➊، ثم تتحقق أن رقم المنطقة (أي أول 3 محارف في text) تحتوي على محارف رقمية فقط ➋، بقية الدالة تتحقق أن السلسلة النصية تتبع نمط أرقام الهواتف: يجب أن تكون أول شرطة في الرقم بعد رمز المنطقة ➌، ثم 3 أرقام ➍، ثم شرطة ➎، ثم 4 أرقام ➏، وإن أنهينا كل عمليات التحقق بنجاح فستعيد الدالة True ➐. استدعاء الدالة isPhoneNumber()‎ مع الوسيط '‎415‎-555-4242' سيعيد True، بينما استدعاؤها مع 'ABC' سيعيد False، إذ ستفشل أول عملية تحقق لأن السلسلة النصية 'ABC' ليست بطول 12 محرفًا. إذا أردت العثور على رقم هاتف في سلسلة نصية طويل، فستحتاج إلى كتابة المزيد من الشيفرات لمطابقة نمط رقم الهاتف. بدِّل استدعاء الدالة print()‎ في المثال السابق إلى ما يلي: message = 'Call me at 415-555-1011 tomorrow. 415-555-9999 is my office.' for i in range(len(message)): ➊ chunk = message[i:i+12] ➋ if isPhoneNumber(chunk): print('Phone number found: ' + chunk) print('Done') سينتج البرنامج الناتج الآتي: Phone number found: 415-555-1011 Phone number found: 415-555-9999 Done في كل دورة لحلقة for سنأخذ 12 محرفًا من المتغير message ونسندها إلى المتغير chunk ➊، فمثلًا في أول دورة لحلقة التكرار تكون قيمة i هي 0، وستسند القيمة message[0:12]‎ إلى المتغير chunk (أي السلسلة النصية 'Call me at 4')، وفي الدورة التالية تكون قيمة i تساوي 1، وستسند القيمة message[1:13]‎ إلى chunk (أي السلسلة النصية 'all me at 41')، بعبارة أخرى: في كل دورة من حلقة for ستتغير قيمة chunk بزيادة مكان بدء عملية الاقتطاع بمقدار واحد، ويكون طولها 12 محرفًا: 'Call me at 4' 'all me at 41' 'll me at 415' 'l me at 415-‎' وهلم جرًا… سنمرر بعدئذٍ المتغير chunk إلى الدالة isPhoneNumber()‎ للتحقق إن كان يطابق نمط أرقام الهواتف ➋، وإذا طابقها فسنطبع القيمة المطابقة. سنستمر في تنفيذ حلقة التكرار التي ستمر على السلسلة النصية كلها، وستختبر كل 12 محرفًا فيها على حدة، وتطبع قيمة chunk إن أعادت الدالة isPhoneNumber()‎ القيمة True، وبعد المرور على جميع محارف السلسلة النصية message فسنطبع الكلمة Done. على الرغم من أن السلسلة النصية في هذا المثال message قصيرة، لكنها قد تكون طويلة جدًا وفيها ملايين المحارف، وسيعالجها البرنامج في أقل من ثانية؛ لكن إن كتبنا برنامجًا يطابق أرقام الهواتف باستخدام التعابير النمطية فسيعمل بسرعة مشابهة تقريبًا، لكن برمجته أسهل بكثير. العثور على تعبير نصي باستخدام التعابير النمطية يعمل البرنامج الذي كتبناه في القسم السابق عملًا صحيحًا، لكننا احتجنا إلى كتابة شيفرة كثيرة للقيام بأمر بسيط؛ فطول الدالة isPhoneNumber()‎ هو 17 سطرًا لكنها تستطيع مطابقة نمط واحد من أرقام الهواتف. فماذا عن الأرقام المكتوبة بالشكل 415.555.4242 أو ‎(415) 555-4242؟ ماذا لو كان هنالك رمز تحويل بعد رقم الهاتف مثل ‎415-555-4242 x99؟ ستفشل الدالة isPhoneNumber()‎ بمطابقتها. صحيحٌ أنك تستطيع كتابة شيفرات إضافية للتحقق من هذه الحالات، لكن هنالك طريقة أسهل بكثير. التعابير النمطية Regular Expression أو اختصارًا Regex تصف نمط النصوص (ومن هنا أتى اسمها). فمثلًا ‎\d في صياغة التعابير النمطية تعني محرف رقمي، أي رقم مفرد من 0 حتى 9. ويستعمل التعبير النمطي ‎\d\d\d-\d\d\d-\d\d\d\d في بايثون لمطابقة النص الذي تطابقه الدالة isPhoneNumber()‎ السابقة: سلسلة من 3 أرقام، ثم شرطة، ثم 3 أرقام، ثم شرطة، ثم 4 أرقام. ولن تطابق أي سلسلة نصية لها نمط آخر التعبير النمطي ‎\d\d\d-\d\d\d-\d\d\d\d. لكن يمكن أن تكون التعابير النمطية أعقد من ذلك بكثير، فمثلًا إضافة الرقم 3 بين قوسين مجعدين {3} يعني «طابق هذا النمط 3 مرات»، وبالتالي يمكننا كتابة التعبير النمطي السابق بشكل مختصر ‎\d{3}-\d{3}-\d{4}‎ وستكون النتيجة نفسها. إنشاء كائنات Regex جميع دوال التعابير النمطية موجودة في الوحدة re، وعلينا استيرادها أولًا: >>> import re ملاحظة: أغلبية الأمثلة في هذا المقال تستعمل الوحدة re، لذا من المهم أن تتذكر استيرادها في بداية كل سكربت أو حينما تعيد تشغيل محرر Mu، وإلا فستحصل على رسالة الخطأ NameError: name 're' is not defined. تمرير سلسلة نصية تمثل التعبير النمطي إلى re.compile()‎ سيعيد كائن Regex. لإنشاء كائن Regex يطابق نمط أرقام الهواتف السابق، فأدخل ما يلي إلى الطرفية التفاعلية. تذكر أن ‎\d تعني «محرف رقمي»، و ‎\d\d\d-\d\d\d-\d\d\d\d هو التعبير النمطي الذي سيطابق رقم الهاتف. >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') يحتوي المتغير phoneNumRegex الآن على كائن Regex. مطابقة كائنات Regex يمتلك الكائن Regex التابع search()‎ الذي يبحث في السلسلة النصية التي مررت إليها لأي مطابقة للتعبير النمطية. سيعيد التابع search()‎ القيمة None إذا لم يُطابَق التعبير النمطي في السلسلة النصية، وإذا عُثر على التعبير النمطي فسيعيد التابع search()‎ كائن Match، الذي فيه التابع group()‎ الذي يعيد النص الذي جرت مطابقته مع التعبير النمطي (سنشرح المجموعات لاحقًا فلا تقلق): >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') >>> mo = phoneNumRegex.search('My number is 415-555-4242.') >>> print('Phone number found: ' + mo.group()) Phone number found: 415-555-4242 اسم المتغير mo هو اسم عام لكائنات Match، قد يبدو المثال السابق معقدًا لكنه أقصر بكثير من برنامج isPhoneNumber.py. بدايةً مررنا التعبير النمطي الذي نريده إلى re.compile()‎ وحفظنا كائن Regex الناتج في المتغير phoneNumRegex، ثم استدعينا التابع search()‎ على phoneNumRegex ومررنا السلسلة النصية التي نريد البحث فيها عن النمط إلى التابع search()‎. ستخزن نتيجة البحث في المتغير mo، ونحن نعرف في هذا المثال أن التابع سيعيد الكائن Match، ولمعرفتنا أن المتغير mo سيحتوي على كائن Match وليس قيمة فارغة None، فاستدعينا التابع group()‎ على mo لإعادة الناتج، ولأننا كتبنا mo.group()‎ داخل الدالة print()‎ فسنعرض الناتج الذي جربت مطابقته 415‎-555-4242. مراجعة لعملية لمطابقة التعابير النمطية هنالك عدة خطوات لاستعمال التعابير النمطية في بايثون، وكل واحدة منها بسيطة وسهلة: استيراد الوحدة Regex بكتابة import re. إنشاء كائن Regex مع الدالة re.compile()‎، وتذكر أن تستعمل سلسلة نصية خام raw بكتابة r قبلها. تمرير السلسلة النصية التي تريد البحث فيها إلى التابع search()‎ الذي سيعيد كائن من النوع Match. استدعاء الدالة group()‎ للكائن Match التي تعيد السلسلة النصية المطابقة. ملاحظة: صحيحٌ أنني أنصحك أن تجرب الشيفرات في الطرفية التفاعلية لكنك تستطيع استخدام تطبيقات ويب لاختبار التعابير النمطية التي تظهر لك بوضوح كيف يطابق التعبير النمطي النص الذي أدخلته. أنصحك بتجربة موقع pythex. ميزات إضافية لمطابقة النصوص عبر التعابير النمطية لقد تعلمنا الخطوات الأساسية لإنشاء والبحث عبر التعابير النمطية في بايثون، وأصبحنا جاهزين لتجربة ميزات أقوى لها. التجميع مع الأقواس لنقل أنك تريد أن تفصل رقم المنطقة من بقية رقم الهاتف. إذا أضفنا أقواسًا في التعابير النمطية فستنشِئ مجموعات groups كما في (‎\d\d\d)-(\d\d\d-\d\d\d\d)، ثم يمكننا استخدام التابع group()‎ للكائن Match للحصول على النص المطابق من مجموعة معينة. ستخزن القيمة المطابقة من أول مجموعة في المجموعة 1، والمجموعة الثانية في 2… وإذا مررنا القيمة 1 أو 2 إلى التابع group()‎ فسنحصل على أجزاء مختلفة من النص الذي جرت مطابقته. أما تمرير القيمة 0 أو عدم تمرير أي قيمة إلى التابع group()‎ فسيعيد كامل النص الذي جرت مطابقته من التعبير النمطي: >>> phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)') >>> mo = phoneNumRegex.search('My number is 415-555-4242.') >>> mo.group(1) '415' >>> mo.group(2) '555-4242' >>> mo.group(0) '415-555-4242' >>> mo.group() '415-555-4242' إذا أردت الحصول على جميع المجموعات في آن واحد، فاستعمل التابع groups()‎ (لاحظ أن اسم التابع بالجمع وليس المفرد): >>> mo.groups() ('415', '555-4242') >>> areaCode, mainNumber = mo.groups() >>> print(areaCode) 415 >>> print(mainNumber) 555-4242 يعيد التابع mo.groups()‎ صفًا tuple فيه أكثر من قيمة، ويمكنك استعمال الإسناد المتعدد لإسناد كل قيمة إلى متغير كما في السطر الآتي: areaCode, mainNumber = mo.groups()‎ للأقواس معنى خاص في التعابير النمطية، لكن ماذا نفعل لو أردنا مطابقة الأقواس في النص؟ لنقل مثلًا أن رمز المنطقة في أرقام الهواتف التي تريد مطابقتها موجودة بين قوسين، وفي هذه الحالة سنحتاج إلى تهريب المحرفين ( و ) عبر شرطة مائلة خلفية: >>> phoneNumRegex = re.compile(r'(\(\d\d\d\)) (\d\d\d-\d\d\d\d)') >>> mo = phoneNumRegex.search('My phone number is (415) 555-4242.') >>> mo.group(1) '(415)' >>> mo.group(2) '555-4242' سيطابق المحرفان المهربان ‎`)‎و ‎`(‎ في السلسلة النصية الخام الممررة إلى الدالة re.compile()‎ الأقواس في السلسلة النصية التي سنبحث فيها. هنالك معانٍ خاصة للمحارف الآتية في التعابير النمطية: . ^ $ * + ? { } [ ] \ | ( ) في حال أردت مطابقة أحد تلك المحارف في النص الذي تريد البحث فيه، فعليك تهريبها بوضع خط مائل خلفي قبلها: \. \^ \$ \* \+ \? \{ \} \[ \] \\ \| \( \) تأكد أنك لم تخلط بين الأقواس المهربة وبين أقواس المجموعات في التعابير النمطية، إذا ظهرت لك رسالة خطأ حول «قوس ناقص» أو «أقواس غير متوازنة» فاعلم أنك نسيت إغلاق قوس مجموعة غير مهرب كما في المثال الآتي: >>> re.compile(r'(\(Parentheses\)') Traceback (most recent call last): --snip-- re.error: missing ), unterminated subpattern at position 0 تقول لك رسالة الخطأ أن هنالك قوس مفتوح في الفهرس 0 من السلسلة النصية r'((Parentheses)'‎ الذي لا يوجد له قوس إغلاق. مطابقة أكثر من مجموعة باستخدام الخط العمودي محرف الخط العمودي | الذي يسمى أيضًا بالأنبوب Pipe يستعمل في أي مكان تريد فيه مطابقة أحد التعابير الموفرة، أي لنقل أن لدينا التعبير النمطي r'Batman|Yasmin'‎ الذي سيطابق 'Batman' أو 'Yasmin'. فلو كانت الكلمتان Batman و Yasmin موجودةً في السلسلة النصية التي سنبحث فيها، فستعاد أول قيمة تطابق إلى الكائن Match: >>> heroRegex = re.compile (r'Batman|Yasmin') >>> mo1 = heroRegex.search('Batman and Yasmin') >>> mo1.group() 'Batman' >>> mo2 = heroRegex.search('Yasmin and Batman') >>> mo2.group() 'Yasmin' ملاحظة: يمكنك العثور على جميع حالات المطابقة باستخدام التابع findall()‎ المشروحة في هذا المقال. يمكنك أيضًا استخدام الخط العمودي لمطابقة تعابير فرعية مختلفة، فمثلًا لو قلنا أننا نريد مطابقة أي سلسلة نصية من 'Batman' و 'Batmobile' و 'Batcopter' و 'Batbat'، ولأن كل هذه السلاسل النصية تبدأ بالكلمة Bat فمن المنطقي أن نكتب هذه السابقة مرة واحدة، ويمكننا فعل ذلك عبر الأقواس كما يلي: >>> batRegex = re.compile(r'Bat(man|mobile|copter|bat)') >>> mo = batRegex.search('Batmobile lost a wheel') >>> mo.group() 'Batmobile' >>> mo.group(1) 'Mobile' استدعاء التابع mo.group()‎ يعيد النص 'Batmobile' بينما mo.group(1)‎ يعيد النص المطابق داخل مجموعة الأقواس الأولى، أي 'mobile'. استخدام الخط العمودي | يتيح لنا مطابقة أحد التعابير النمطية الموفرة، وإذا أردنا أن نطابق الخط العمودي نفسه في السلسلة النصية المبحوث فيها فلا ننسى تهريبه ‎|‎. المطابقة الاختيارية عبر إشارة الاستفهام قد ترغب أحيانًا بمطابقة نمط اختياريًا، أي أن التعبير النمطي سيطابق السلسلة النصية بغض النظر إن احتوت السلسلة النصية على ذاك النمط أم لا. وتشير علامة الاستفهام ? أن النمط الذي يسبقها اختياري: >>> batRegex = re.compile(r'Bat(wo)?man') >>> mo1 = batRegex.search('The Adventures of Batman') >>> mo1.group() 'Batman' >>> mo2 = batRegex.search('The Adventures of Batwoman') >>> mo2.group() 'Batwoman' لاحظ أن الجزء ‎(wo)?‎ يعني أن النمط wo هو مجموعة اختيارية، فسيطابَق التعبير النمطي لو احتوت السلسلة النصية على 0 أو 1 من السلسلة النصية wo داخلها. وهذا يعني أن التعبير النمطي سيطابق 'Batwoman' و 'Batman' معًا. لو عدنا إلى مثال أرقام الهواتف السابق، يمكننا تعديل التعبير النمطي لكي يبحث عن أرقام الهواتف التي تحتوي أو لا تحتوي على رمز المنطقة: >>> phoneRegex = re.compile(r'(\d\d\d-)?\d\d\d-\d\d\d\d') >>> mo1 = phoneRegex.search('My number is 415-555-4242') >>> mo1.group() '415-555-4242' >>> mo2 = phoneRegex.search('My number is 555-4242') >>> mo2.group() '555-4242' يمكنك أن تقول أن ? يعني «طابق النمط الفرعي السابق 0 مرة أو مرة واحدة»، وإذا أردنا مطابقة علامة الاستفهام فلا ننسى تهريبها بكتابة ‎\?‎. المطابقة صفر مرة أو أكثر باستخدام رمز النجمة رمز النجمة * asterisk يعني «طابق صفر مرة أو أكثر»، فاستعمال النجمة يعني أن التعبير النمطي الذي يسبقها سيطابق لأي عدد من المرات في السلسلة النصية؛ فقد لا يكون موجودًا أو يكون مكررًا مرات كثيرة: >>> batRegex = re.compile(r'Bat(wo)*man') >>> mo1 = batRegex.search('The Adventures of Batman') >>> mo1.group() 'Batman' >>> mo2 = batRegex.search('The Adventures of Batwoman') >>> mo2.group() 'Batwoman' >>> mo3 = batRegex.search('The Adventures of Batwowowowoman') >>> mo3.group() 'Batwowowowoman' لاحظ أن التعبير النمطي ‎(wo)*‎ سيُطابَق 0 مرة في 'Batman'، ومرة واحدة في 'Batwoman'، وأربع مرات في 'Batwowowowoman'. إذا أردنا مطابقة النجمة نفسها فلا ننسَ تهريبها بكتابة ‎\*‎. المطابقة مرة واحدة أو أكثر باستخدام إشارة الجمع على خلاف إشارة النجمة * التي تطابق النمط صفر مرة أو أكثر، تعني إشارة الجمع أو إشارة الزائد + «مطابقة النمط مرة واحدة أو أكثر»، هذا يعني أن النمط الذي يسبق إشارة + يجب أن يكون موجودًا على الأقل مرة واحدة، على خلاف إشارة النجمة التي تسمح بألّا يكون النمط موجودًا في السلسلة النصية. قارن بين أثر رمز النجمة في القسم السابق والمثال الآتي: >>> batRegex = re.compile(r'Bat(wo)+man') >>> mo1 = batRegex.search('The Adventures of Batwoman') >>> mo1.group() 'Batwoman' >>> mo2 = batRegex.search('The Adventures of Batwowowowoman') >>> mo2.group() 'Batwowowowoman' >>> mo3 = batRegex.search('The Adventures of Batman') >>> mo3 == None True التعبير النمطي Bat(wo)+man لا يطابق السلسلة النصية 'The Adventures of Batman' لأن من المطلوب وجود التعبير الفرعي wo مرة واحدة على الأقل. إذا أردنا مطابقة إشارة الزائد فلا ننسى تهريبها ‎+‎. مطابقة النمط لعدد معين من المرات باستخدام الأقواس المجعدة إذا كانت لديك مجموعة تريد تكرارها لعدد معين من المرات، فأتبع تلك السلسلة برقم محاط بأقواس {}. فمثلًا التعبير النمطي ‎(Ha){3}‎ يطابق السلسلة النصية 'HaHaHa' لكنه لن يطابق 'HaHa' لأنها تحتوي على تكرارين للتعبير Ha فقط. وبدلًا من كتابة رقم واحد، يمكننا تحديد مجال من التكرارات بكتابة الحد الأدنى، ثم فاصلة، ثم الحد الأقصى ضمن القوسين، مثلًا التعبير النمطي ‎(Ha){3,5}‎ سيطابق 'HaHaHa' و 'HaHaHaHa' و 'HaHaHaHaHa'. يمكنك ألّا تحدد الرقم الأدنى أو الأقصى ضمن القوسين لتترك المجال مفتوحًا، فمثلًا ‎(Ha){3,}‎ سيطبق 3 تكرارات أو أكثر من المجموعة (Ha)، بينما ‎(Ha){,5}‎ سيطابق المجموعة (Ha) من 0 حتى 5 تكرارات. ستساعدنا الأقواس المجعدة على تقصير التعبير النمطي، إذ يتساوى التعبيران النمطيان الآتيان: (Ha){3} (Ha)(Ha)(Ha) وسيتساوي أيضًا: (Ha){3,5} ((Ha)(Ha)(Ha))|((Ha)(Ha)(Ha)(Ha))|((Ha)(Ha)(Ha)(Ha)(Ha)) جرب ما يلي في الطرفية التفاعلية: >>> haRegex = re.compile(r'(Ha){3}') >>> mo1 = haRegex.search('HaHaHa') >>> mo1.group() 'HaHaHa' >>> mo2 = haRegex.search('Ha') >>> mo2 == None True سيطابق ‎(Ha){3}‎ السلسلة النصية 'HaHaHa' لكنه لن يطابق 'Ha'، وبالتالي سيعيد التابع search()‎ القيمة None. المطابقة الجشعة والمطابقة غير الجشعة لمّا كان التعبير ‎(Ha){3,5}‎ يطابق 3 أو 4 أو 5 تكرارات من Ha في السلسلة النصية 'HaHaHaHaHa' فستتسائل لماذا يعيد استدعاء التابع group()‎ على الكائن Match السلسلة النصية 'HaHaHaHaHa' بدلًا من الاحتمالات الأخرى، ففي النهاية 'HaHaHa' و 'HaHaHaHa' هي مطابقات صالحة في التعبير النمطي ‎(Ha){3,5}‎. تكون التعابير النمطية في بايثون جشعة greedy افتراضيًا، وهذا يعني أنه في الحالات غير المحددة ستحاول التعابير النمطية مطابقة أطول سلسلة نصية ممكنة؛ أما المطابقة غير الجشعة non-greedy (وتسمى أحيانًا بالمطابقة الكسولة lazy) تطابق أقصر سلسلة نصية ممكنة، ونستطيع تحديد أننا نريد مطابقة غير جشعة عبر وضع علامة استفهام بعد قوس الإغلاق المجعد. جرب ما يلي ولاحظ الاختلافات بين النسخة الجشعة وغير الجشعة وماذا ستطابق في السلسلة النصية نفسها: >>> greedyHaRegex = re.compile(r'(Ha){3,5}') >>> mo1 = greedyHaRegex.search('HaHaHaHaHa') >>> mo1.group() 'HaHaHaHaHa' >>> nongreedyHaRegex = re.compile(r'(Ha){3,5}?') >>> mo2 = nongreedyHaRegex.search('HaHaHaHaHa') >>> mo2.group() 'HaHaHa' لاحظ أن علامة الاستفهام لها معنيان في التعابير النمطية، الأول هو المطابقة غير الجشعة، والثاني هو جعل المجموعة اختيارية. وهذان المعنيان غير مرتبطين ببعضهما. التابع findall()‎ بالإضافة إلى التابع search()‎ تمتلك كائنات Regex التابع findall()‎، وفي حين أن التابع search()‎ يعيد كائن Match لأول جزء مطابَق من السلسلة النصي التي نبحث فيها، يعيد التابع findall()‎ جميع المطابقات في السلسلة النصية. لنرى كيف يعيد التابع search()‎ الكائن Match على أول سلسلة نصية مطابقة: >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') >>> mo = phoneNumRegex.search('Cell: 415-555-9999 Work: 212-555-0000') >>> mo.group() '415-555-9999' وفي المقابل لن يعيد التابع findall()‎ الكائن Match، بل قائمة فيها سلاسل نصية، لطالما لم تكن هنالك مجموعات فرعية في التعبير النمطي؛ وتمثل كل سلسلة نصية في القائمة المعادة الجزء من النص المطابق للتعبير النمطي: >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') # لا توجد تعابير فرعية >>> phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000') ['415-555-9999', '212-555-0000'] أما لو كانت هنالك مجموعات أو تعابير فرعية في التعبير النمطي، فسيعيد التابع findall()‎ قائمةً من الصفوف tuples، ويمثل كل صف مطابقةً، وعناصر الصف هي السلاسل النصية المطابقة لكل تعبير فرعي في التعبير النمطي. لنرى الكلام السابق عمليًا لنفهمه، جرب ما يلي في الطرفية التفاعلية ولاحظ وجود أنماط فرعية في التعبير النمطي: >>> phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)') # توجد تعابير فرعية >>> phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000') [('415', '555', '9999'), ('212', '555', '0000')] لنلخص ما الذي يعيد التابع findall()‎: عند استدعائه على تعبير نمطي لا يحتوي على مجموعات، مثل ‎\d\d\d-\d\d\d-\d\d\d\d فسيعيد التابع findall()‎ قائمةً من السلاسل النصية مثل ‎['415-555-9999', '212-555-0000']‎. عند استدعائه على تعبير نمطي يحتوي على مجموعات أو تعابير نمطية فرعية، مثل (‎\d\d\d)-(\d\d\d)-(\d\d\d\d) فسيعيد التابع findall()‎ قائمةً من الصفوف التي تحتوي على سلاسل نصية، سلسلة نصية لكل مجموعة، مثل: ‎[('415', '555', '9999'), ('212', '555', '0000')]‎ فئات المحارف تعلمنا في مثال أرقام الهواتف السابق أن ‎\d يطابق أي رقم، أي أنه اختصار للتعبير ‎(0|1|2|3|4|5|6|7|8|9)‎. هنالك عدد من فئات المحارف المختصرة، والتي تستطيع معرفتها من الجدول 7-1. اختصار فئة المحارف يمثل \d أي محرف يمثل رقمًا من 0 حتى 9 \D أي محرف ليس رقمًا من 0 حتى 9 \w أي حرف أو رقم أو الشرطة السفلية، يمكننا أن نقول أنه يطابق محارف «الكلمات» \W أي محرف ليس حرفًا أو رقمًا أو شرطةً سفلية (عكس ‎\w) \s أي مسافة فارغة أو مسافة جدولة أو سطر جديد، يمكننا أن نقول أنه يطابق «الفراغات» \S أي محرف ليس فراغًا أو مسافة جدولة أو سطر جديد الجدول 7-1: اختصارات فئات المحارف الشائعة تساعد فئات المحارف Character classes على اختصار التعابير النمطية، ففئة المحارف [0‎-5] تطابق الأرقام من 0 إلى 5 فقط، وهذا أكثر اختصارًا من كتابة (0‎|1|2|3|4|5)، لاحظ أننا نملك \d لمطابقة الأرقام فقط، لكن ‎\w يطابق الأرقام والأحرف والشرطة السفلية _، ولا يوجد اختصار لمطابقة الأحرف فقط، لكنك تستطيع أن تكتب [a-zA-Z] التي سنشرحها لاحقًا. >>> xmasRegex = re.compile(r'\d+\s\w+') >>> xmasRegex.findall('12 drummers, 11 pipers, 10 lords, 9 ladies, 8 maids, 7 swans, 6 geese, 5 rings, 4 birds, 3 hens, 2 doves, 1 partridge') ['12 drummers', '11 pipers', '10 lords', '9 ladies', '8 maids', '7 swans', '6 geese', '5 rings', '4 birds', '3 hens', '2 doves', '1 partridge'] سيطابق التعبير النمطي ‎\d+\s\w+‎ أي نص فيه رقم واحد أو أكثر ‎\d+‎ يكون متبوعًا بفراغ ‎\s ويكون متبوعًا برقم أو حرف أو شرطة سفلية ‎\w+‎. سيعيد التابع findall()‎ السلاسل النصية المطابقة من التعبير النمطي في قائمة. كتابة فئات محارف مخصصة هنالك حالات نحتاج فيها إلى استخدام مجموعة من المحارف لكن الفئات المختصرة الشائعة (مثل ‎\d و ‎\w و ‎\s …إلخ.) غير مناسبة لحالتنا؛ لذا يمكننا تعريف فئات المحارف بأنفسنا باستعمال الأقواس المربعة []. فمثلًا فئة المحارف [aeiouAEIOU] ستطابق أي حرف صوتي سواءً كان بالحالة الصغيرة أو الكبيرة: >>> vowelRegex = re.compile(r'[aeiouAEIOU]') >>> vowelRegex.findall('RoboCop eats baby food. BABY FOOD.') ['o', 'o', 'o', 'e', 'a', 'a', 'o', 'o', 'A', 'O', 'O'] يمكنك أيضًا تضمين مجال من الأرقام أو الأحرف باستخدام الشرطة، فمثلًا فئة المحارف [a-zA-Z0-9] ستطابق جميع الأحرف الصغيرة والأحرف الكبيرة والأرقام. لاحظ أن رموز التعابير النمطية العادية لن تفسر داخل الأقواس المربعة، فلا حاجة إلى تهريب المحارف . أو * أو ? أو () بخط مائل خلفي. فمثلًا فئة المحارف [‎0-5.‎] تطابق الأعداد من 0 إلى 5 ونقطة. ولا حاجة إلى تهريبها وكتابة [0‎-5\.‎]. إذا وضعنا رمز القبعة caret ^ بعد قوس بداية فئة المحارف فسيعني «الرفض»، أي سيعكس محتويات فئة المحارف، أي أنها ستطابق أي محرف ليس موجودًا في فئة المحارف المحددة: >>> consonantRegex = re.compile(r'[^aeiouAEIOU]') >>> consonantRegex.findall('RoboCop eats baby food. BABY FOOD.') ['R', 'b', 'C', 'p', ' ', 't', 's', ' ', 'b', 'b', 'y', ' ', 'f', 'd', '.', ' ', 'B', 'B', 'Y', ' ', 'F', 'D', '.'] فبدلًا من مطابقة الأحرف الصوتية، أصبحت فئة المحارف تطابق كل شيء عدا الأحرف الصوتية. رمز القبعة ورمز الدولار يمكننا أيضًا استخدام رمز القبعة ^ في بداية التعبير النمطي للإشارة أن المطابقة يجب أن تبدأ من بداية السلسلة النصية التي نبحث فيها عن النمط. وبالمثل يمكننا استخدام رموز الدولار $ في نهاية التعبير النمطي للإشارة أنه يجب أن تنتهي السلسلة النصية بالتعبير النمطي. يمكننا أن نستعمل الرمزين ^ و $ معًا للإشارة إلى أن السلسلة النصية كلها يجب أن تطابق التعبير النمطي، أي لا يسمح بإجراء مطابقة جزئية على السلسلة النصية فقط. لنأخذ مثالًا بسيطًا التعبير النمطي r'^Hello'‎ الذي سيطابق أي سلسلة نصية تبدأ بالكلمة 'Hello': >>> beginsWithHello = re.compile(r'^Hello') >>> beginsWithHello.search('Hello, world!') <re.Match object; span=(0, 5), match='Hello'> >>> beginsWithHello.search('He said hello.') == None True التعبير النمطي ‎r'\d$'‎ يطابق السلاسل النصية التي تنتهي برقم من 0 إلى 9: >>> endsWithNumber = re.compile(r'\d$') >>> endsWithNumber.search('Your number is 42') <re.Match object; span=(16, 17), match='2'> >>> endsWithNumber.search('Your number is forty two.') == None True أما التعبير ‎r'^\d+$'‎ فهو يطابق السلاسل النصية التي تحتوي على رقم واحد على الأقل: >>> wholeStringIsNum = re.compile(r'^\d+$') >>> wholeStringIsNum.search('1234567890') <re.Match object; span=(0, 10), match='1234567890'> >>> wholeStringIsNum.search('12345xyz67890') == None True >>> wholeStringIsNum.search('12 34567890') == None True لاحظ أن آخر استدعائين للتابع search()‎ يوضحان كيف يجب أن تطابق السلسلة النصية كلها النمط، وليس جزءًا منها. حرف البدل تسمى النقطة . في التعابير النمطية بحرف البدل wildcard، وهي تطابق أي محرف عدا السطر الجديد. فمثلًا: >>> atRegex = re.compile(r'.at') >>> atRegex.findall('The cat in the hat sat on the flat mat.') ['cat', 'hat', 'sat', 'lat', 'mat'] تذكر أن النقطة ستطابق محرفًا واحدًا فقط، وهذا هو السبب في أن التعبير النمطي طابق lat في السلسلة النصية flat فقط. إذا أردنا البحث عن رمز النقطة فلا ننسى تهريبه عبر خط مائل خلفي ‎\.‎. مطابقة كل شيء مع النقطة والنجمة نحتاج أحيانًا إلى مطابقة كل شيء، فمثلًا لو أردنا مطابقة السلسلة النصية 'First Name‎:‎' متبوعةً بأي نصف، فحينها سنستعمل النقطة والنجمة ‎.*‎. تذكر أن النقطة تعني أي حرف عدا السطر الجديد، والنجمة تعني تكرار النمط الذي يسبقها 0 مرة أو أكثر. >>> nameRegex = re.compile(r'First Name: (.*) Last Name: (.*)') >>> mo = nameRegex.search('First Name: Al Last Name: Sweigart') >>> mo.group(1) 'Al' >>> mo.group(2) 'Sweigart' تجري النقطة والنجمة مطابقةً جشعةً، أي أنها تحاول أن تطابق النصوص أكثر ما يمكن، ولجعلها مطابقة غير جشعة فيمكننا استخدام النقطة والنجمة وإشارة الاستفهام ‎.*?‎ وبالتالي ستطابق بايثون النمط مطابقةً غير جشعة. جرب المثال الآتي لترى الفرق بين النسخة الجشعة وغير الجشعة: >>> nongreedyRegex = re.compile(r'<.*?>') >>> mo = nongreedyRegex.search('<To serve man> for dinner.>') >>> mo.group() '<To serve man>' >>> greedyRegex = re.compile(r'<.*>') >>> mo = greedyRegex.search('<To serve man> for dinner.>') >>> mo.group() '<To serve man> for dinner.>' يمكننا ترجمة التعبيرين النمطيين السابقين إلى «طابق قوس بدء زاوية ثم أي شيء ثم قوس إغلاق زاوية»، لكن السلسلة النصية '‎<To serve man> for dinner.>‎' فيها مطابقتين لقوس إغلاق زاوية، ففي النسخة غير الجشعة تطابق بايثون أقصر سلسلة نصية ممكنة '<To serve man>'؛ أما في النسخة الجشعة فتحاول بايثون مطابقة أطول سلسلة نصية ممكنة: '‎<To serve man> for dinner.>‎'. مطابقة الأسطر الجديدة مع رمز النقطة تعلمنا أن النقطة والنجمة ستطابق كل المحارف عدا السطر الجديد، لكن بتمرير وسيط ثاني إلى الدالة re.compile()‎ هو re.DOTALL فسنتمكن من استعمال النقطة لمطابقة جميع المحارف بما فيها السطر الجديد: >>> noNewlineRegex = re.compile('.*') >>> noNewlineRegex.search('Serve the public trust.\nProtect the innocent. \nUphold the law.').group() 'Serve the public trust.' >>> newlineRegex = re.compile('.*', re.DOTALL) >>> newlineRegex.search('Serve the public trust.\nProtect the innocent. \nUphold the law.').group() 'Serve the public trust.\nProtect the innocent.\nUphold the law.' التعبير النمطي noNewlineRegex لم نمرر إليه re.DOTALL حين استدعاء re.compile()‎ لذا سيطابق النمط كل شيء حتى الوصول إلى محرف السطر الجديد؛ بينما newlineRegex مررنا إليه re.DOTALL حين استدعاء re.compile()‎ لذا سيطابق كل شيء، لهذا أعاد استدعاء newlineRegex.search()‎ السلسلة النصية كاملة بما فيها الأسطر الجديدة. مراجعة لرموز التعابير النمطية شرحنا الكثير من الرموز في هذا المقال، لنراجع سريعًا ما الذي تعلمناه حول أساسيات التعابير النمطية: يطابق ? المجموعة التي تسبقه 0 مرة أو 1 مرة. يطابق * المجموعة التي تسبقه 0 مرة أو أكثر. يطابق + المجموعة التي تسبقه 1 مرة أو أكثر. يطابق {n} المجموعة التي تسبقه n مرة تمامًا. يطابق {‎n,‎} المجموعة التي تسبقه n مرة أو أكثر. يطابق {‎, m} المجموعة التي تسبقه 0 مرة حتى m مرة. يطابق {n, m} المجموعة التي تسبقه n مرة على الأقل و m على الأكثر. تجري ‎{n,m}?‎ أو ‎*?‎ أو ‎+?‎ مطابقة غير جشعة. يطابق ‎^spam السلاسل النصية التي تبدأ بالكلمة spam. يطابق spam$‎ السلاسل النصية التي تنتهي بالكلمة spam. يطابق الرمز . أي محرف عدا السطر الجديد. تطابق فئة المحارف ‎\d و ‎\w و ‎\s رقمًا أو كلمةً أو فراغًا على التوالي وبالترتيب. تطابق فئة المحارف ‎\D و ‎\W و ‎\S أي شيء عدا أن يكون رقمًا أو كلمةً أو فراغًا على التوالي وبالترتيب. تطابق فئة المحارف [abc] أي شيء بين القوسين المربعين (مثل a أو b أو c). تطابق فئة المحارف [‎^abc] أي شيء ليس بين القوسين المربعين. المطابقة غير الحساسة لحالة الأحرف تطابق التعابير النمطية النص بنفس الحالة المحددة، فمثلًا تطابق التعابير النمطية الآتية سلاسل نصية مختلفة: >>> regex1 = re.compile('RoboCop') >>> regex2 = re.compile('ROBOCOP') >>> regex3 = re.compile('robOcop') >>> regex4 = re.compile('RobocOp') لكن في بعض الأحيان لا يهمنا ما هي حالة الأحرف، وكل ما نريده هو مطابقة النص بغض النظر عن حالته، فحينها نريد جعل التعابير النمطية غير حساسة لحالة الأحرف، وذلك بتمرير وسيطٍ ثانٍ للدالة re.compile()‎ هو re.IGNORECASE أو re.I: >>> robocop = re.compile(r'robocop', re.I) >>> robocop.search('RoboCop is part man, part machine, all cop.').group() 'RoboCop' >>> robocop.search('ROBOCOP protects the innocent.').group() 'ROBOCOP' >>> robocop.search('Al, why does your programming book talk about robocop so much?').group() 'robocop' استبدال السلاسل النصية عبر التابع sub()‎ لا تستعمل التعابير النمطية للعثور على أنماط من النصوص فقط، وإنما لاستبدالها أيضًا. يقبل التابع sub()‎ في كائنات Regex معاملين، الأول هو السلسلة النصية التي نريد تبديل المطابقات إليها، والثاني هو السلسلة النصية التي نريد البحث فيها عن مطابقات لتبديلها. يعيد التابع sub()‎ سلسلةً نصية بعد تطبيق جميع عمليات الاستبدال: >>> namesRegex = re.compile(r'Agent \w+') >>> namesRegex.sub('CENSORED', 'Agent Alice gave the secret documents to Agent Bob.') 'CENSORED gave the secret documents to CENSORED.' قد نحتاج أحيانًا إلى استخدام النص المطابق كجزء من النص الجديد، لذا يمكننا استخدام ‎\1 و ‎\2 و ‎\3 في الوسيط الأول الممرر إلى التابع sub()‎ للوصول إلى المجموعات الفرعية المطابقة. لنقل أننا نريد أن نخفي أسماء الأشخاص في مثالنا السابق، لكننا نريد إظهار الحرف الأول من اسمهم فقط، فحينها نستطيع استخدام التعبير النمطي Agent (\w)\w*‎ وتمرير r'\1‎****'‎ كأول معامل إلى التابع sub()‎، وستشير ‎\1 إلى السلسلة النصية المطابقة من النمط الفرعي الأول، أي المجموعة (‎\w) في التعبير النمطي: >>> agentNamesRegex = re.compile(r'Agent (\w)\w*') >>> agentNamesRegex.sub(r'\1****', 'Agent Alice told Agent Carol that Agent Eve knew Agent Bob was a double agent.') A**** told C**** that E**** knew B**** was a double agent.' التعامل مع التعابير النمطية المعقدة التعابير النمطية التي تطابق نصًا بسيطًا تكون سهلة الفهم، لكن إذا أردنا مطابقة النصوص المعقدة فستصبح التعابير النمطية معقدة وطويلة، يمكننا التعامل مع هذا الإشكال بالطلب من الدالة re.compile()‎ أن تتجاهل الفراغات والتعليقات داخل السلسلة النصية التي تمثل التعبير النمطي، وهذا النمط يسمى verbose mode، ويمكن تفعيله بتمرير re.VERBOSE إلى المعامل الثاني في الدالة re.compile()‎. فبدلًا من محاولة قراءة تعبير نمطي صعب مثل هذا: phoneRegex = re.compile(r'((\d{3}|\(\d{3}\))?(\s|-|\.)?\d{3}(\s|-|\.)\d{4} (\s*(ext|x|ext.)\s*\d{2,5})?)') نستطيع تقسيم التعبير النمطي إلى عدة أسطر مع تعليقات توضح وظيفة كل قسم: phoneRegex = re.compile(r'''( (\d{3}|\(\d{3}\))? # رمز المنطقة (\s|-|\.)? # فاصل \d{3} # أول 3 أرقام (\s|-|\.) # فاصل \d{4} # آخر 4 أرقام (\s*(ext|x|ext.)\s*\d{2,5})? # اللاحقة )''', re.VERBOSE) لاحظ أننا استعملنا علامة الاقتباس الثلاثية لإنشاء سلسلة نصية متعددة الأسطر، لكي نستطيع تقسيم التعبير على عدة أسطر وتسهيل قراءته. تتبع التعليقات داخل التعابير النمطية نفس قواعد التعليقات في بايثون: سيتم تجاهل كل شيء بعد الرمز #. لاحظ أن الفراغات الزائدة في السلسلة النصية متعددة الأسطر لا تؤثر على معنى التعبير النمطي ولا تعد جزءًا منه، لذا يمكنك استخدام الفراغات كيفما تشاء لتسهيل قراءة التعبير النمطي. استخدام re.IGNORECASE و re.DOTALL و re.VERBOSE ماذا لو أردنا استخدام re.VERBOSE لكتابة تعليقات في التعابير النمطية لكننا نريد أن نتجاهل حالة الأحرف أيضًا عبر re.IGNORECASE؟ للأسف لا تقبل الدالة re.compile()‎ غير قيمة واحدة كثاني وسيط لها، لكن يمكننا تجاوز هذه المحدودية بجمع القيم re.IGNORECASE و re.DOTALL و re.VERBOSE مع بضعها عبر الخط العمودي | وهو يسمى في هذا السياق بعامل OR الثنائي Bitwise OR. أي أننا لو أردنا كتابة تعبير نمطي غير حساس لحالة الأحرف ويسمح بمطابقة محرف السطر الجديد برمز النقطة، فحينها سيكون استدعاء الدالة re.compile()‎ كما يلي: >>> someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL) ولو أردنا تضمين الخيارات كلها فنكتب: >>> someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL | re.VERBOSE) أصل هذه الصيغة من الإصدارات القديمة من بايثون، وشرح العوامل الثنائية خارج سياق هذه السلسلة. لاحظ أن هنالك خيارات أخرى يمكن تمريرها إلى الدالة re.compile()‎ لكنها غير شائعة الاستخدام، ويمكنك القراءة عنها في التوثيق الرسمي. مشروع: برنامج استخراج أرقام الهواتف وعناوين البريد الإلكتروني لنقل أنك موكل بمهمة مملة هي العثور على جميع أرقام الهواتف وعناوين البريد الإلكتروني في صفحة ويب طويلة أو مستند كبير. إذا كنت ستتصفح تلك الصفحة وتبحث عنها يدويًا فستأخذ منك وقتًا طويلًا وجهدًا كبيرًا؛ لكن إن كان لديك برنامج يمكنه البحث في الحافظة عن أرقام الهواتف وعناوين البريد الإلكتروني فسيكون ذلك رائعًا، فكل ما عليك فعله هو الضغط على Ctrl+A لتحديد كل النص ثم Ctrl+C لنسخه إلى الحافظة، ثم شغل برنامجك وسيعدل محتويات المحافظة ويضع فيها أرقام الهواتف وعناوين البريد الإلكتروني التي سيعثر عليها. قد يغريك حينما تبدأ بمشروع جديد أن تبدأ كتابة الشيفرات فورًا، لكن من الأفضل أن تنظر نظرةً شاملة على البرنامج قبل البدء بكتابته، وأنصحك أن ترسم خطةً بسيطةً لما يجب على برنامجك فعله، دون التفكير بالشيفرات وكيفية كتابتها، وإنما اكتب العناوين العريضة فقط. فمثلًا لو أردنا العمل على برنامج استخراج أرقام الهواتف وعناوين البريد الإلكتروني فسنحتاج إلى: الحصول على النص من الحافظة. العثور على جميع أرقام الهواتف وعناوين البريد الإلكتروني. لصق الناتج في الحافظة. يمكنك أن تبدأ الآن بالتفكير كيفية كتابة ذلك في بايثون. سيحتاج برنامجك إلى: استخدام الوحدة pyperclip لنسخ ولصق النصوص من الحافظة. إنشاء تعبيران نمطيان، واحد لمطابقة أرقام الهواتف والثاني لمطابقة عناوين البريد الإلكتروني. العثور على جميع المطابقات لكلي التعبيرين النمطيين، وليس أول مطابقة فقط. تنسيق الناتج في سلسلة نصية واحدة جاهزة ولصقها في الحافظة. عرض رسالة خطأ إن لم يعثر على مطابقات في النص. هذا هو المخطط العام للمشروع، وسنركز على كيفية حل كل خطوة حين كتابة الشيفرات. لاحظ أننا تعلمنا كيفية التعامل مع كل خطوة من الخطوات السابقة في بايثون. الخطوة 1: إنشاء تعبير نمطي لأرقام الهواتف علينا بداية إنشاء تعبير نمطي للبحث عن أرقام الهواتف، لننشِئ ملفًا باسم phoneAndEmail.py ونكتب فيه: #! python3 # phoneAndEmail.py - البحث عن أرقام الهواتف وعناوين البريد في الحافظة import pyperclip, re phoneRegex = re.compile(r'''( (\d{3}|\(\d{3}\))? # رمز المنطقة (\s|-|\.)? # فاصل (\d{3}) # أول 3 أرقام (\s|-|\.) # فاصل (\d{4}) # آخر 4 أرقام (\s*(ext|x|ext.)\s*(\d{2,5}))? # اللاحقة )''', re.VERBOSE) # TODO: إنشاء التعبير النمطي لعناوين البريد الإلكتروني # TODO: العثور على المطابقات في الحافظة # TODO: لصق الناتج في الحافظة التعليقات التي تبدأ بالكلمة TODO هي لبنية التطبيق، وسنبدلها إلى شيفرات لاحقًا. يبدأ رقم الهاتف برمز منطقة اختياري، لذا نجعل المجموعة الفرعية التي تدل على رمز المنطقة اختياريةً بإضافة علامة استفهام ?. ولأن رمز المنطقة يمكن أن يكون 3 أرقام (أي ‎\d{3}‎) أو 3 أرقام ضمن أقواس (أي ‎\(\d{3}\)‎) فاستعملنا الخط العمودي | للفصل بينهما. لاحظ أننا استطعنا إضافة التعليقات إلى التعبير النمطي متعدد الأسطر لكي نتذكر ماذا يفعل كل سطر. يمكن أن يكون الفاصل في أرقام الهواتف فراغًا ‎\s أو شرطة - أو نقطة .، لذا فصلنا بين هذه الاحتمالات بخط عمودي. بقية أقسام التعبير النمطي واضحة وسهلة: 3 أرقام، يتبعها فاصل، يتبعها 4 أرقام، وآخر قسم هو اللاحقة الاختيارية التي تبدأ بأي عدد من الفراغات يليها الكلمة ext أو x أو ext.‎، ويليها رقمين إلى 5 أرقام. ملاحظة: من السهل أن نخلط بين التعابير النمطية التي تحتوي على مجموعات عبر استخدام الأقواس ()، وبين الأقواس المُهربة ‎)‎ و ‎(‎. تذكر أن تتأكد أنك تستخدم النوع الصحيح من الأقواس إن حصلت على رسالة خطأ تشير إلى قوسٍ ناقص. الخطوة 2: إنشاء تعبير نمطي لعناوين البريد الإلكتروني علينا الآن إنشاء تعبير نمطي لمطابقة عناوين البريد الإلكتروني: #! python3 # phoneAndEmail.py - البحث عن أرقام الهواتف وعناوين البريد في الحافظة import pyperclip, re phoneRegex = re.compile(r'''( --مقتطع-- # إنشاء التعبير النمطي لعناوين البريد الإلكتروني emailRegex = re.compile(r'''( ➊ [a-zA-Z0-9._%+-]+ # اسم المستخدم ➋ @ # @ إشارة ➌ [a-zA-Z0-9.-]+ # اسم النطاق (\.[a-zA-Z]{2,4}) # اللاحقة )''', re.VERBOSE) # TODO: العثور على المطابقات في الحافظة # TODO: لصق الناتج في الحافظة يكون اسم المستخدم جزءًا من البريد الإلكتروني ➊، وفيه محارف واحدة أو أكثر مما يلي: الأحرف الكبيرة والصغيرة، والأرقام، والنقطة، والشرطة السفلية، وإشارة النسبة المئوية، وإشارة زائد، وشرطة عادية. يمكننا جمع كل ما سبق في فئة المحارف الآتية [a-zA-Z0-9‎._%+-‎]. يفصل بين اسم المستخدم والنطاق برمز @ ➋؛ ولا يسمح اسم النطاق بنفس المحارف التي يسمح بها اسم المستخدم، ولذا يجب أن ننشِئ فئة محارف فيها الأرقام والأحرف والنقط والشرطات [a-zA-Z0-9‎.-‎] ➌. آخر جزء هو اللاحقة، مثل ‎.com (يسمى تقنيًا بالنطاق في أعلى مستوى top-level domain) الذي هو رمز النقطة ويلي أي شيء، ويفترض أن يكون بين 2 و 4 محارف. هنالك قواعد كثيرة جدًا لمطابقة عناوين البريد الإلكتروني، والتعبير النمطي الذي كتبناه لن يطابق جميع عناوين البريد الصالحة قاعديًا، لكنه سيطابق جميع عناوين البريد الشائعة. الخطوة 3: العثور على جميع المطابقات في الحافظة أصبح لدينا الآن التعابير النمطية التي ستطابق أرقام الهواتف وعناوين البريد الإلكتروني عبر الوحدة re في بايثون، واستطعنا الوصول إلى محتويات الحافظة عبر الدالة paste()‎ في الوحدة pyperclip. نحتاج الآن إلى استخدام التابع findall()‎ للكائن Regex لإعادة قائمة من الصفوف. تأكد أن برنامجك يشبه البرنامج الآتي: #! python3 # phoneAndEmail.py - البحث عن أرقام الهواتف وعناوين البريد في الحافظة import pyperclip, re phoneRegex = re.compile(r'''( --مقتطع-- # العثور على المطابقات في الحافظة text = str(pyperclip.paste()) ➊ matches = [] ➋ for groups in phoneRegex.findall(text): phoneNum = '-'.join([groups[1], groups[3], groups[5]]) if groups[8] != '': phoneNum += ' x' + groups[8] matches.append(phoneNum) ➌ for groups in emailRegex.findall(text): matches.append(groups[0]) # TODO: لصق الناتج في الحافظة هنالك صف لكل مطابقة ناتجة، وكل صف يحتوي على السلاسل النصية في كل مجموعة في التعبير النمطي، تذكر أن المجموعة 0 تطابق كامل التعبير النمطي، لذا ما يهمنا من الناتج هو المجموعة في الفهرس 0. سنخزن المطابقات في قائمة باسم matches ➊، وسنبدأ برنامجنا بقائمة فارغة، ثم لدينا حلقتَي for. ففي الحلقة الخاصة بالبريد الإلكتروني نضيف محتويات المجموعة 0 إلى القائمة matches ➌؛ أما في الحلقة الخاصة بأرقام الهواتف فلا نريد أن نضيف ناتج المجموعة 0 إلى القائمة. صحيحٌ أن برنامجنا يستطيع مطابقة أرقام الهواتف بأكثر من صيغة، لكننا نريد إضافة أرقام الهواتف بصيغة واحدة معيارية، لذا ننشِئ المتغير phoneNum الذي يحتوي على قيمة المجموعات 1 و 3 و 5 و 8 من النص المطابق، وهي تمثل رقم المنطقة، وأول 3 أرقام، وآخر 4 أرقام، واللاحقة على التوالي وبالترتيب. الخطوة 4: جمع المطابقات في سلسلة نصية ونسخها إلى الحافظة أصبح لدينا الآن عناوين البريد الإلكتروني وأرقام الهواتف كقائمة من السلاسل النصية المخزنة في matches، إذا أردنا نسخ هذه القائمة إلى الحافظة فسنستعمل الدالة pyperclip.copy()‎ التي تقبل سلسلة نصية، لكن المطابقات موجودة لدينا في قائمة، لذا نحن بحاجة إلى استخدام التابع join()‎ على القائمة matches. للتأكد أن برنامجنا يعمل كما ينبغي له، فلنطبع أي مطابقات إلى الطرفية، وإذا لم تطابق أي عناوين بريدية أو أرقام هواتف فسيخبر البرنامجُ المستخدمَ بذلك. #! python3 # phoneAndEmail.py - البحث عن أرقام الهواتف وعناوين البريد في الحافظة --مقتطع-- for groups in emailRegex.findall(text): matches.append(groups[0]) # Copy results to the clipboard. if len(matches) > 0: pyperclip.copy('\n'.join(matches)) print('Copied to clipboard:') print('\n'.join(matches)) else: print('No phone numbers or email addresses found.') تشغيل البرنامج للتجربة، افتح المتصفح وتوجه إلى صفحة تواصل معنا من موقع nostarch.com، ثم حددها كلها بالضغط على Ctrl+A ثم انسخها إلى الحافظة Ctrl+C، ثم شغل البرنامج. يجب أن يكون الناتج كما يلي: Copied to clipboard: 800-420-7240 415-863-9900 415-863-9950 info@nostarch.com media@nostarch.com academic@nostarch.com info@nostarch.com أفكار لمشاريع مشابهة هنالك تطبيقات كثيرة لعملية مطابقة أنماط من النص (واستبدلها عبر التابع sub()‎)، فمثلًا يمكنك: العثور على جميع روابط URL التي تبدأ بالسابقة http://‎ أو https://‎. العثور على التواريخ بصيغها المختلفة مثل 3/14/2022 أو 03-14-2022 أو 2022/3/14 وتبديلها إلى صيغة واحدة قياسية موحدة. إزالة البيانات الحساسة مثل أرقام البطاقات البنكية من المستندات. العثور على الأخطاء الشائعة مثل الفراغات المكررة بين الكلمات، أو الكلمات المكررة خطأً، أو علامات الترقيم المكررة. الخلاصة صحيحٌ أن الحواسيب قادرة على البحث عن النصوص بسرعة، لكن يجب أن نخبرها تحديدًا ما الذي عليها البحث عنه. تسمح لنا التعابير النمطية أن نحدد نمطًا من المحارف الذي نريد البحث عنه، وتستخدم التعابير النمطية في البرمجة أو حتى في محررات النصوص أو مع برامج معالجة جداول البيانات. تأتي الوحدة re مع أساس لغة بايثون، وتسمح لنا ببناء كائنات Regex، التي تمتلك عددًا من التوابع: التابع search()‎ للبحث عن مطابقة واحدة، والتابع findall()‎ للعثور على جميع المطابقات، والتابع sub()‎ لإجراء عمليات البحث والاستبدال. ستجد المزيد من المعلومات في توثيق بايثون الرسمي عن التعابير النمطية، وفي موقع regular-expressions.info. مشاريع تدريبية لكي تتدرب، اكتب برامج لتنفيذ المهام الآتية. التحقق من التاريخ اكتب تعبيرًا نمطيًا يستطيع التعرف على التواريخ بالصيغة DD/MM/YYYY، افترض أن الأيام تتراوح بين 01 إلى 31، والأشهر من 01 إلى 12، والسنوات من 1000 إلى 2999. لاحظ أنه إذا كان اليوم أو الشهر متألف من رقم واحد فيجب أن يسبق بصفر 0. لا حاجة أن يتعرف التعبير النمطي على الأرقام الصحيحة لكل شهر من أشهر السنة، أو إذا كانت السنوات كبيسة؛ فلا بأس أن يقبل تاريخًا مثل 31/02/2022 أو 31/04/2022. خزن السلاسل النصية الناتج في متغيرات باسم month و day و year، ثم اكتب شيفرة تتحقق إن كان التاريخ صالحًا، فأشهر نسيان/أبريل وحزيران/يونيو وأيلول/سبتمبر وتشرين الثاني/نوفمبر لها 30 يومًا، وشهر شباط/فبراير له 28 يومًا، وبقية الأشهر 31 يومًا. يكون طول شهر شباط/فبراير 29 يومًا في السنوات الكبيسة، والسنة الكبيرة هي كل سنة تكون قابلة للقسمة على 4، عدا السنوات القابلة للقسمة على 100؛ ما لم تكن السنة قابلة للقسم على 400. لاحظ أن هذه العمليات الحسابية تجعل من المستحيل كتابة تعبير نمطي يمكنه فعل كل ذلك. التحقق من كلمة مرور قوية اكتب دالةً تستخدم التعابير النمطية للتأكد أن كلمة المرور الممررة إليها قوية. يمكننا تعريف كلمة المرور القوية أنها بطول 8 محارف على الأقل، وتحتوي على أحرف كبيرة وصغيرة، وفيها رقم واحد على الأقل. قد تحتاج إلى اختبار كلمة المرور على أكثر من تعبير نمطي للتأكد من قوتها. نسخة من الدالة strip()‎ باستخدام التعابير النمطية اكتب دالةً تقبل سلسلةً نصيةً وتفعل فيها كما تفعله الدالة strip()‎ التي تعلمناها في المقال السابق. إذا لم يمرر إليها أي وسائل بخلاف السلسلة النصية، فسنحذف جميع الفراغات من بداية ونهاية السلسلة النصية؛ وإلا فسنحذف المحارف المحددة في الوسيط الثاني الممرر إلى الدالة. ترجمة -بتصرف- للفصل Pattern Matching With Regular Expressions من كتاب Automate the Boring Stuff with Python. اقرأ أيضًا المقال السابق: معالجة النصوص باستخدام لغة بايثون python التعابير النمطية في البرمجة القوائم Lists في لغة بايثون تهيئة بيئة العمل في بايثون Python
  2. النصوص هي أكثر أنواع البيانات التي ستتعامل معها في برنامجك. لقد تعلمت كيفية جمع سلسلتين نصيتين مع بعضها عبر العامل +، لكنك تستطيع أكثر من ذلك بكثير؛ إذ تستطيع استخراج سلاسل نصية فرعية، وإضافة وحذف الفراغات، وتحويل الأحرف إلى أحرف كبيرة أو صغيرة، والتحقق من تنسيق السلاسل تنسيقًا صحيحًا؛ وتستطيع أيضًا كتابة برنامج بايثون للوصول إلى الحافظة في حاسوبك لنسخ ولصق النصوص. كل ما سبق ستتعلمه في هذا المقال وسنزيد على ذلك؛ وسنرى مشروعين عمليين: حافظة بسيطة التي تخزن عدة سلاسل نصية من النصوص، وبرنامج لأتمتة العملية المملة لتنسيق النصوص يدويًا. التعامل مع السلاسل النصية لنلقِ نظرةً على الطرائق التي تسمح لنا فيها بايثون بالكتابة والطباعة والوصول إلى السلاسل النصية في برامجنا. السلاسل النصية المجردة كتابة القيم النصية في شيفرة بايثون هو أمر بالغ السهولة: نبدأ وننتهي بعلامة اقتباس مفردة. لكن ماذا سيحدث لو أردنا استخدام علامة اقتباس داخل السلسلة النصية؟ إذا كتبنا ‎'That is Alice's cat.'‎ فسيحدث خطأ لأن بايثون ستظن أن السلسلة النصية قد انتهت بعد Alice وبقية السطر s cat.'‎ هي شيفرة غير صالحة في بايثون. لحسن الحظ هنالك عدة طرائق لكتابة السلاسل النصية. علامات الاقتباس المزدوجة يمكن أن تبدأ السلاسل النصية وتنتهي عبر كتابة علامتي اقتباس مزدوجتين، وهي تعمل كما في علامات الاقتباس المفردة. إحدى فوائد استخدام علامات الاقتباس المزدوجة هي إمكانية كتابة علامة الاقتباس الفردية داخلها: >>> spam = "That is Alice's cat." ولأن السلسلة النصية تبدأ بعلامة اقتباس مزدوجة، فستعلم بايثون أن علامة الاقتباس المفردة هي جزء من السلسلة النصية ولا تمثل نهايتها. لكن ماذا لو احتجنا إلى استخدام علامات الاقتباس المفردة والمزدوجة معًا في السلسلة النصية؟ سنحتاج هنا إلى محارف التهريب. محارف التهريب تسمح لنا محارف التهريب escape characters باستخدام محارف ليس من الممكن إدراجها مباشرةً في السلسلة النصية. يتألف محرف التهريب من خط مائل خلفي backslash \ متبوعًا بأحد المحارف التي نريد إضافتها إلى السلسلة النصية؛ وصحيحٌ أن اسمه هو «محرف» التهريب، لكن يتألف من محرفين ويشار إليه عادةً بصيغة المفرد escape character. فمثلًا محرف التهريب لعلامة الاقتباس المفردة هو ‎\'‎ وذب الشيفرة الآتية: >>> spam = 'Say hi to Bob\'s brother.' ولأننا وضعنا خط مائل خلفي قبل علامة الاقتباس المفردة في Bob\'s فستعلم بايثون أننا لا نقصد أن ننهي السلسلة النصية. تسمح لنا محارف التهريب\' و \" بوضع علامات الاقتباس داخل السلاسل النصية المحاطة بعلامات اقتباس مفردة ومزدوجة على الترتيب. الجدول 6-1 يوضح بعض محارف التهريب التي تستطيع استخدامها. فيما يلي محارف التهريب: ‎\'‎: علامة اقتباس مفردة ‎\"‎: علامة اقتباس مزدوجة ‎\t: علامة الجدولة tab ‎\n: سطر جديد newline \\: خط مائل خلفي جرب السلسلة النصية الآتية: >>> print("Hello there!\nHow are you?\nI\'m doing fine.") Hello there! How are you? I'm doing fine. السلاسل النصية الخام يمكنك وضع الحرف r قبل علامة الاقتباس في بداية السلسلة النصية لجعلها سلسلة نصية خام. السلسلة النصية الخام raw string تتجاهل جميع محارف التهريب وتظهر جميع الخطوط المائلة الخلفية كما هي: >>> print(r'That is Carol\'s cat.') That is Carol\'s cat. ولأنها سلسلة نصية خام فستعدّ بايثون الخط الخلفي المائل على أنه جزء من السلسلة النصية وليس جزءًا من محرف التهريب. يمكن أن تكون السلاسل النصية الخام مفيدةً إن كانت تكتب سلاسل نصية فيها عدد من الخطوط المائلة الخلفية، مثل مسارات الملفات في نظام ويندوز r'C:\Users\Al\Desktop'‎ أو التعابير النمطية regular expressions المشروحة في المقال القادم. السلاسل النصية متعددة الأسطر بعلامات اقتباس ثلاثية صحيحٌ أن بإمكاننا استخدام محرف التهريب ‎\n لطابعة سطر جديد داخل سلسلة نصية، لكن من الأسهل استخدام السلاسل النصية متعددة الأسطر. تبدأ السلسلة النصية متعددة الأسطر في بايثون بثلاث علامات اقتباس مفردة أو مزدوجة، وستعد جميع علامات الاقتباس ومسافات الجدولة tabs والأسطر الجديدة على أنها جزءٌ من السلسلة النصية المحاطة بعلامات اقتباس ثلاثية. لاحظ أن قواعد تنسيق بايثون الخاصة بالمسافات البادئة في الكتل البرمجية لا تنطبق على السلاسل النصية متعددة الأسطر. print('''Dear Alice, Eve's cat has been arrested for catnapping, cat burglary, and extortion. Sincerely, Bob''') احفظ ما سبق في ملف باسم catnapping.py وشغله: Dear Alice, Eve's cat has been arrested for catnapping, cat burglary, and extortion. Sincerely, Bob لاحظ أن علامة الاقتباس المفردة في Eve's لا تحتاج إلى تهريب، فتهريب علامات الاقتباس المفردة والمزدوجة هو أمرٌ اختياري حين استخدام السلاسل النصية متعددة الأسطر. السلسلة النصية الموجودة في دالة print()‎ الآتية تكافئ المثال السابق دون استخدام السلاسل النصية متعددة الأسطر: print('Dear Alice,\n\nEve\'s cat has been arrested for catnapping, cat burglary, and extortion.\n\nSincerely,\nBob') التعليقات متعددة الأسطر صحيحٌ أن رمز المربع # يرمز إلى بداية تعليق حتى نهاية السطر، لكن من الشائع استخدام سلسلة نصية متعددة الأسطر للتعليقات التي تمتد لأكثر من سطر. الشيفرات الآتية صالحة تمامًا في بايثون: """This is a test Python program. Written by Al Sweigart al@inventwithpython.com This program was designed for Python 3, not Python 2. """ def spam(): """This is a multiline comment to help explain what the spam() function does.""" print('Hello!') فهرسة وتقسيم السلاسل النصية تستعمل السلاسل النصية الفهارس ويمكن تقسيمها كما في القوائم lists. يمكنك أن تعدّ السلسلة النصية ‎'Hello, world!'‎ على أنها قائمة وكل حرف فيها يمثل عنصرًا في تلك القائمة مع فهرس مرتبط به. ' H e l l o , w o r l d ! ' 0 1 2 3 4 5 6 7 8 9 10 11 12 لاحظ تضمين الفراغ وإشارة التعجب، وسيكون عدد الأحرف هو 13، بدءًا من الحرف H في الفهرس 0 إلى الحرف ! في الفهرس 12. >>> spam = 'Hello, world!' >>> spam[0] 'H' >>> spam[4] 'o' >>> spam[-1] '!' >>> spam[0:5] 'Hello' >>> spam[:5] 'Hello' >>> spam[7:] 'world!' إذا حددت فهرسًا فستحصل على المحرف الموجود في ذاك الموضع في السلسلة النصية، وإذا حددت مجالًا من فهرسٍ ما إلى آخر فسيُضمَّن المحرف الموجود في فهرس البداية ولن يضمن المحرف الموجود في فهرس النهاية، ولذا إذا كان لدينا المتغير spam الذي فيه ‎'Hello, world!'‎ فإن spam[0:5]‎ هو 'Hello'؛ فالسلسلة النصية الجزئية التي ستحصل عليها من spam[0:5]‎ ستتضمن كل محرف موجود في المجال spam[0]‎ حتى spam[4]‎ ولن تحتوي الفاصلة الموجودة في الفهرس 5 ولا الفراغ في المحرف 6. هذا السلوك يشبه سلوك الدالة range(5)‎ التي ستؤدي -حين استعمالها مع for- إلى المرور على الأرقام حتى الرقم 5 دون تضمينه. لاحظ أن تقسيم السلاسل النصية لا يغير السلسلة النصية الأصلية، ويمكننا حفظ القيمة الناتجة في متغير منفصل: >>> spam = 'Hello, world!' >>> fizz = spam[0:5] >>> fizz 'Hello' بتقسيم السلسلة النصية ثم تخزينها في متغير آخر فسنتمكن من الوصول إلى السلسلة النصية الأصلية والسلسلة النصية المقتطعة بسهولة. العوامل in و not in مع السلاسل النصية يمكن استخدام العاملين in و not in مع السلاسل النصية كما في القوائم lists. التعبير البرمجي الذي فيه سلسلتين نصيتين مجموعٌ بينها بالعامل in أو not in سينتج القيمة المنطقية True أو False: >>> 'Hello' in 'Hello, World' True >>> 'Hello' in 'Hello' True >>> 'HELLO' in 'Hello, World' False >>> '' in 'spam' True >>> 'cats' not in 'cats and dogs' False تختبر التعابير البرمجية السابقة إن كانت السلسلة النصية الأولى (كما هي بحذافيرها، مع حالة الأحرف فيها) موجودةً في السلسلة النصية الثانية. وضع السلاسل النصية داخل سلاسل نصية أخرى من الشائع في البرمجة وضع سلسلة نصية داخل سلسلة نصية أخرى، واستعملنا حتى الآن العامل + لجمع السلاسل النصية كما يلي: >>> name = 'Al' >>> age = 4000 >>> 'Hello, my name is ' + name + '. I am ' + str(age) + ' years old.' 'Hello, my name is Al. I am 4000 years old.' لكن ذلك يتطلب كتابةً كثيرة، ومن الأسهل أن ندس السلاسل النصية داخل بعضها string interpolation، وبهذه الطريقة نستعمل العامل ‎%s داخل السلاسل النصية كمؤشر لكي يستبدل مسبقًا إلى القيم التي تلي السلسلة النصية. إحدى ميزات استخدام هذه الطريقة هو عدم حاجتنا إلى استدعاء الدالة str()‎ لتحويل القيم إلى سلاسل نصية: >>> name = 'Al' >>> age = 4000 >>> 'My name is %s. I am %s years old.' % (name, age) 'My name is Al. I am 4000 years old.' أضافت بايثون 3.6 ما يسمى بالسلاسل النصية المنسقة f-strings، وهي تشبه دس السلاسل النصية لكنها تتيح إضافة التعابير البرمجية بين قوسين مجعدين مباشرةً؛ تذكر أن تضيف الحرف f قبل علامة الاقتباس الابتدائية في السلسلة النصية المنسقة: >>> name = 'Al' >>> age = 4000 >>> f'My name is {name}. Next year I will be {age + 1}.' 'My name is Al. Next year I will be 4001.' إذا لم تتذكر تضمين الحرف f قبل علامة الاقتباس فستعامل الأقواس على أنها جزء من السلسلة النصية: >>> 'My name is {name}. Next year I will be {age + 1}.' 'My name is {name}. Next year I will be {age + 1}.' توابع مفيدة للتعامل مع السلاسل النصية يشرح هذا القسم أكثر التوابع شيوعًا التي تحلل السلاسل النصية وتعالجها وتنتج سلاسل نصية معدلة. التوابع upper()‎ و lower()‎ و isupper()‎ و islower()‎ يعيد التابعان upper()‎ و lower()‎ سلسلةً نصيةً جديدةً تحوَّل فيها أحرف السلسلة النصية الأصلية إلى حالة الأحرف الكبيرة أو الصغيرة على التوالي. أما المحارف غير النصية أو غير اللاتينية فتبقى كما هي: >>> spam = 'Hello, world!' >>> spam = spam.upper() >>> spam 'HELLO, WORLD!' >>> spam = spam.lower() >>> spam 'hello, world!' لاحظ أن هذان التابعان لا يغيران السلسلة النصية الأصلية وإنما يعيدان سلسلةً نصيةً جديدةً. إذا أردت تعديل السلسلة النصية الأصلية فعليك استدعاء التابع upper()‎ أو lower()‎ على السلسلة النصية ثم إسناد الناتج مباشرةً إلى المتغير الذي يحتوي على السلسلة النصية الأصلية؛ أي أننا سنكتب شيئًا يشبه spam = spam.upper()‎ لتعديل السلسلة النصية المخزنة في المتغير spam بدلًا من كتابة spam.upper()‎ فقط، وهذا يشبه حالة وجود متغير اسمه eggs يحتوي القيمة 10، فكتابة التعبير البرمجي eggs + 3 لا يؤدي إلى تغيير القيمة المخزنة في المتغير eggs مباشرة، وإنما علينا كتابة eggs = eggs + 3. التابعان upper()‎ و lower()‎ مفيدان إن أردنا مقارنة سلسلتين نصيتين مقارنةً غير حساسةٍ لحالة الأحرف. فمثلًا السلسلتان النصيتان 'great' و 'GREat' غير متساويتين؛ لكن قد لا يهمنا في بعض الحالات التي نطلب فيها مدخلات المستخدم إن كان قد كتب Great أم GREAT أم gREAt. انظر المثال الآتي الذي نحول فيه السلسلة النصية إلى حالة الأحرف الصغيرة: print('How are you?') feeling = input() if feeling.lower() == 'great': print('I feel great too.') else: print('I hope the rest of your day is good.') حينما تشغل البرنامج السابق فسيظهر لك سؤال، وإذا أدخلت الكلمة great بأي شكل من الأشكال فستظهر لك العبارة ‎'I feel great too.'‎. من المفيد جدًا إضافة آلية للتعامل مع اختلاف حالات الأحرف في مدخلات المستخدم في برامجك، إذ سيسهل ذلك استخدامها كثيرًا. How are you? GREat I feel great too. يعيد التابعان isupper()‎ و islower()‎ قيمةً منطقية True إن كانت السلسلة النصية تحتوي على حرف واحد على الأقل وكان ذاك الحرف كبيرًا أو صغيرًا على التوالي وبالترتيب؛ وإلا فستعيد القيمة False: >>> spam = 'Hello, world!' >>> spam.islower() False >>> spam.isupper() False >>> 'HELLO'.isupper() True >>> 'abc12345'.islower() True >>> '12345'.islower() False >>> '12345'.isupper() False ولأن القيمة المعادة من التابعين upper()‎ و lower()‎ هي سلاسل نصية، فيمكننا استدعاء توابع التعامل مع السلاسل النصية على القيم المعادة من تلك التوابع مباشرةً، وتبدو هذه التعابير البرمجية على أنها سلسلة من التوابع وراء بعضها كما في المثال الآتي: >>> 'Hello'.upper() 'HELLO' >>> 'Hello'.upper().lower() 'hello' >>> 'Hello'.upper().lower().upper() 'HELLO' >>> 'HELLO'.lower() 'hello' >>> 'HELLO'.lower().islower() True مجموعة توابع isX()‎ بالإضافة إلى التابعين islower()‎ و isupper()‎ هنالك عدد من التوابع التي يبدأ اسمها بالكلمة is، وتعيد تلك التوابع قيمةً منطقية التي تشرح طبيعة السلسلة النصية. هذه بعض تلك التوابع: isalpha()‎ يعيد True إذا احتوت السلسلة النصية على أحرف عادية فقط ولم تكن فارغة. isalnum()‎ يعيد True إذا احتوت السلسلة النصية على أحرف عادية وأرقام فقط ولم تكن فارغة. isdecimal()‎ يعيد True إذا احتوت السلسلة النصية على أرقام فقط ولم تكن فارغة. isspace()‎ يعيد True إذا احتوت السلسلة النصية على فراغات عادية ومسافات جدولة tabs وأسطر جديدة newlines فقط ولم تكن فارغة. istitle()‎ يعيد True إذا بدأت السلسلة النصية بحرف كبير متبوعٌ بمجموعة من الأحرف الصغيرة. لنجرب تلك التوابع عمليًا: >>> 'hello'.isalpha() True >>> 'hello123'.isalpha() False >>> 'hello123'.isalnum() True >>> 'hello'.isalnum() True >>> '123'.isdecimal() True >>> ' '.isspace() True >>> 'This Is Title Case'.istitle() True >>> 'This Is Title Case 123'.istitle() True >>> 'This Is not Title Case'.istitle() False >>> 'This Is NOT Title Case Either'.istitle() False تفيد توابع السلاسل النصية isX()‎ حينما نريد التحقق من مدخلات المستخدم، فالبرنامج الآتي يطلب من المستخدم إدخال عمره وكلمة مرور صالحة: while True: print('Enter your age:') age = input() if age.isdecimal(): break print('Please enter a number for your age.') while True: print('Select a new password (letters and numbers only):') password = input() if password.isalnum(): break print('Passwords can only have letters and numbers.') طلبنا من المستخدم في أول حلقة while أن يدخل عمره، ونخزنه في المتغير age. إذا كانت قيمة age هي قيمة عددية صحيحة، فسنخرج من أول حلقة while وندخل في الثانية التي تسأله عن كلمة المرور. وإلا فسنسأل المستخدم عن عمره مجددًا. في حلقة while الثانية طلبنا كلمة المرور وخزناها في المتغير password، وسنخرج من الحلقة إن كانت كلمة المرور تتألف من أرقام أو أحرف، وإلا فسنطلب من المستخدم إدخال كلمة مرور صالحة مجددًا. Enter your age: forty two Please enter a number for your age. Enter your age: 42 Select a new password (letters and numbers only): secr3t! Passwords can only have letters and numbers. Select a new password (letters and numbers only): secr3t تمكّنا من التحقق من صلاحية مدخلات المستخدم في المثال السابق باستخدام التابعين isdecimal()‎ و isalnum()‎، ولم نقبل كتابة forty two بل قبلنا 42، ولم نقبل secr3t!‎ بل قبلنا secr3t. التابعان startswith()‎ و endswith()‎ يعيد التابعان startswith()‎ و endswith()‎ القيمة True إن بدأت أو انتهت السلسلة النصية التي استدعت عليها (على التوالي) بالسلسلة النصية الممررة إلى التابع؛ وإلا فستعيد False: >>> 'Hello, world!'.startswith('Hello') True >>> 'Hello, world!'.endswith('world!') True >>> 'abc123'.startswith('abcdef') False >>> 'abc123'.endswith('12') False >>> 'Hello, world!'.startswith('Hello, world!') True >>> 'Hello, world!'.endswith('Hello, world!') True هذه التوابع هي بديل مفيد لعامل المساواة == إذا أردنا التحقق من الجزء الأول أو الأخير من السلسلة النصية فقط، بدلًا من مقارنتها كلها. التابعان join()‎ و split()‎ يفيد التابع join()‎ حينما يكون لدينا قائمة فيها سلاسل نصية ونريد أن نجمعها كلها مع بعضها بعضًا في سلسلة نصية واحدة؛ ويستدعى التابع join()‎ على سلسلة نصية، ويقبل معاملًا هو قائمة list فيها سلاسل نصية، ويعيد سلسلةً نصيةً تساوي دمج السلاسل النصية كلها: >>> ', '.join(['cats', 'rats', 'bats']) 'cats, rats, bats' >>> ' '.join(['My', 'name', 'is', 'Simon']) 'My name is Simon' >>> 'ABC'.join(['My', 'name', 'is', 'Simon']) 'MyABCnameABCisABCSimon' لاحظ أن السلسلة النصية التي استدعينا عليها التابع join()‎ أصبحت موجودة بين كل سلسلتين نصيتين في القائمة الممررة كوسيط. فمثلًا حين استدعاء join(['cats', 'rats', 'bats'])‎ على السلسلة النصية ‎', '‎ فيكون الناتج هو ‎'cats, rats, bats'‎. تذكر أن التابع join()‎ يستدعى على سلسلة نصية ونمرر إليه قائمة، وليس العكس. يفعل التابع split()‎ عكس فعل التابع join()‎ تمامًا: يستدعى على سلسلة نصية وتعيد قائمةً من السلاسل النصية: >>> 'My name is Simon'.split() ['My', 'name', 'is', 'Simon'] ستُقسَم السلسلة النصية 'My name is Simon' افتراضيًا عند كل محرف يمثل فراغًا (سواءً كان فراغًا عاديًا ' ' أو محرف جدولة tab أو سطرًا جديدًا newline)، ولن تضمن الفراغات في السلاسل النصية الموجودة في القائمة المعادة من استدعاء هذا التابع. يمكننا تمرير محرف الفصل إلى التابع split()‎ لتحديد محرف آخر غير محارف الفراغات: >>> 'MyABCnameABCisABCSimon'.split('ABC') ['My', 'name', 'is', 'Simon'] >>> 'My name is Simon'.split('m') ['My na', 'e is Si', 'on'] من الشائع استدعاء التابع split()‎ لتقسيم سلسلة نصية متعددة الأسطر في مكان وقوع محرف السطر الجديد: >>> spam = '''Dear Alice, How have you been? I am fine. There is a container in the fridge that is labeled "Milk Experiment." Please do not drink it. Sincerely, Bob''' >>> spam.split('\n') ['Dear Alice,', 'How have you been? I am fine.', 'There is a container in the fridge', 'that is labeled "Milk Experiment."', '', 'Please do not drink it.', 'Sincerely,', 'Bob'] يسمح لنا استدعاء التابع split()‎ مع تمرير محرف السطر الجديد '‎\n' إلى تقسيم سلسلة نصية متعددة الأسطر إلى قائمة يمثل فيها كل عنصر سطرًا من الأسطر. تقسيم السلاسل النصية باستخدام التابع partition()‎ يمكن أن يقسم التابع partition()‎ سلسلةً نصية إلى أقسام، ويعمل بتمرير سلسلة نصية إليه كفاصل، التي سيبحث عنها في السلسلة النصية التي استدعي عليها، ويعيد صفًا tuple فيه السلسلة النصية التي تسبق الفاصل، والفاصل، والسلسلة النصية التي تلي الفاصل: >>> 'Hello, world!'.partition('w') ('Hello, ', 'w', 'orld!') >>> 'Hello, world!'.partition('world') ('Hello, ', 'world', '!') إذا احتوت السلسلة النصية التي تستدعي التابع partition()‎ عليها على أكثر من تكرار للفاصل، فستقسم السلسلة النصية عند أول وقوع له فقط: >>> 'Hello, world!'.partition('o') ('Hell', 'o', ', world!') وإن لم يعثر على الفاصل في السلسلة النصية، فستعاد السلسلة النصية التي استدعي عليها التابع كأول عنصر في الصف، وستكون السلسلتان النصيتان الباقيتان فارغتين: >>> 'Hello, world!'.partition('XYZ') ('Hello, world!', '', '') يمكننا استخدام نشر المتغيرات بالإسناد الجماعي لإسناد السلاسل النصية المعادة إلى ثلاثة متغيرات: >>> before, sep, after = 'Hello, world!'.partition(' ') >>> before 'Hello,' >>> after 'world!' يفيد التابع partition()‎ إن كنت تحتاج إلى الحصول على السلسلة النصية التي تسبق فاصلًا محددًا، والفاصل نفسه، والسلسلة النصية التي تلي ذاك الفاصل. محاذاة النصوص عبر rjust()‎ و ljust()‎ و center()‎ يعيد التابعان rjust()‎ و ljust()‎ سلسلةً نصية محاطة بفراغات افتراضيًا. يمثل أول وسيط يمرر إلى تلك التوابع قيمةً لعدد الفراغات المحيطة بالسلسلة النصية: >>> 'Hello'.rjust(10) ' Hello' >>> 'Hello'.rjust(20) ' Hello' >>> 'Hello, World'.rjust(20) ' Hello, World' >>> 'Hello'.ljust(10) 'Hello ' التابع 'Hello'.rjust(10)‎ يعني أننا نريد محاذاة السلسلة النصية 'Hello' إلى اليمين ويكون طول السلسلة النصية الكاملة هو 10. ولما كانت 'Hello' هي 5 محارف، فستضاف 5 فراغات على يسارها، مما يؤدي إلى إعادة سلسلة نصية طولها 10 محارف وتكون فيها محاذاة 'Hello' على اليمين. وسيطٌ اختياري للتابعين rjust()‎ و ljust()‎ يحدد محرفًا للملء بخلاف الفراغ: >>> 'Hello'.rjust(20, '*') '***************Hello' >>> 'Hello'.ljust(20, '-') 'Hello---------------' التابع center()‎ يعمل مثل التابعين ljust()‎ و rjust()‎ لكنه يوسِّط النص بدلًا من محاذاته إلى اليسار أو اليمين: >>> 'Hello'.center(20) ' Hello ' >>> 'Hello'.center(20, '=') '=======Hello========' تفيد هذه التوابع حينما نريد طباعة جداول من البيانات ويكون لها تباعد صحيح. اكتب البرنامج الآتي واحفظه في الملف picnicTable.py: def printPicnic(itemsDict, leftWidth, rightWidth): print('PICNIC ITEMS'.center(leftWidth + rightWidth, '-')) for k, v in itemsDict.items(): print(k.ljust(leftWidth, '.') + str(v).rjust(rightWidth)) picnicItems = {'sandwiches': 4, 'apples': 12, 'cups': 4, 'cookies': 8000} printPicnic(picnicItems, 12, 5) printPicnic(picnicItems, 20, 6) عرفنا الدالة printPicnic()‎ التي تأخذ قاموسًا كمعامل لها، واستخدمنا التوابع center()‎ و ljust()‎ و rjust()‎ لطباعة المعلومات بصيغة جميلة تشبه الجدول. القاموس الذي سنمرره إلى الدالة printPicnic()‎ هو picnicItems، ولدينا في القاموس picnicItems 4 صندويشات و 12 تفاحة و 4 كاسات و 8,000 كعكة (نعم ثمانية آلاف!). نرغب بتنظيم هذه المعلومات في عمودين، ونضع اسم العنصر على اليسار والكمية على اليمين. لفعل ذلك نحتاج إلى أن نقرر كم سيكون عرض العمودين الأيسر والأيمن؛ لذا نحتاج إلى تمرير هاتين القيمتين مع القاموس إلى الدالة printPicnic()‎ عبر المعاملين leftWidth لعرض العمود الأيسر من الجدول و rightWidth لعرض العمود الأيمن من الجدول. ستطبع الدالة printPicnic()‎ عنوانًا متوسطًا فوق العنصر PICNIC ITEMS، ثم ستمر على عناصر القاموس وتطبع كل زوج من القيم في سطر وتحاذي المفتاح على اليسار وله حاشية متألفة من فواصل، والقيمة على اليمين ولها حاشية متألف من فراغات. بعد تعريف الدالة printPicnic()‎ سنعرف القاموس picnicItems ونستدعي الدالة printPicnic()‎ مرتين، مع تمرير عرض مختلف للعمودين الأيسر والأيمن؛ يكون فيها عرض العمود الأيسر 12 والأيمن 5 في المرة الأولى، ثم 20 و 6 في المرة الثانية. ---PICNIC ITEMS-- sandwiches.. 4 apples...... 12 cups........ 4 cookies..... 8000 -------PICNIC ITEMS------- sandwiches.......... 4 apples.............. 12 cups................ 4 cookies............. 8000 رأينا كيف استفدنا من rjust()‎ و ljust()‎ و center()‎ لطباعة سلاسل نصية منسقة تنسيقًا جميلًا، حتى لو لم نكن نعرف كم سيكون العدد الدقيق لمحارف السلسلة النصية التي سنطبعها. حذف الفراغات عبر strip()‎ و rstrip()‎ و lstrip()‎ نحتاج أحيانًا إلى حذف الفراغات البيضاء (التي هي محارف الفراغ العادي ومسافة الجدولة tab والسطر الجديد newline) من الطرف الأيسر أو الأيمن أو من كلا طرفي السلسلة النصية. سيعيد التابع strip()‎ سلسلةً نصيةً جديدةً لا تحتوي على أي فراغات في بدايتها أو نهايتها، بينما يحذف التابعان lstrip()‎ و rstrip()‎ الفراغات البيضان من الطرف الأيسر أو الأيمن على التوالي: >>> spam = ' Hello, World ' >>> spam.strip() 'Hello, World' >>> spam.lstrip() 'Hello, World ' >>> spam.rstrip() ' Hello, World' يمكننا تمرير وسيط اختياري يحتوي على المحارف التي نريد حذفها بدلًا من الفراغات: >>> spam = 'SpamSpamOliveSpamEggsSpamSpam' >>> spam.strip('ampS') 'OliveSpamEggs' تمرير الوسيط 'ampS' إلى التابع strip()‎ سيؤدي إلى حذف جميع تكرارات المحارف a و m و p و S من بداية ونهاية السلسلة النصية spam. لاحظ أن ترتيب الأحرف في السلسلة النصية الممررة إلى التابع strip()‎ لا يهم، فكتابة strip('ampS')‎ تكافئ كتابة strip('mapS')‎ أو strip('Spam')‎. القيم العددية للأحرف مع الدالتين ord()‎ و chr()‎ تخزن الحواسيب البيانات على هيئة بايتات، وهي سلاسل من البتات بنظام العد الثنائي، وهذا يعني أننا نحتاج إلى تحويل النصوص إلى أرقام لكي نستطيع تخزينها؛ ولهذا يكون لكل محرف character قيمة رقمية مرتبطة به تسمى Unicode code point. فمثلًا القيمة الرقمية 65 تمثل 'A'، والقيمة 52 تمثل '4'، والقيمة 33 تمثل '!'. يمكننا استخدام الدالة ord()‎ للحصول على القيمة الرقمية لسلسلة نصية تحتوي محرفًا واحدًا، والدالة chr()‎ للحصول على المحرف الذي وفرنا قيمته الرقمية: >>> ord('A') 65 >>> ord('4') 52 >>> ord('!') 33 >>> chr(65) 'A' تفيد هذه الدوال حينما نحتاج إلى ترتيب المحارف أو إجراء عمليات رياضية عليها: >>> ord('B') 66 >>> ord('A') < ord('B') True >>> chr(ord('A')) 'A' >>> chr(ord('A') + 1) 'B' هنالك تفاصيل كثيرة عن يونيكود وأرقام المحارف، لكنها خارجة عن سياق هذه السلسلة. نسخ ولصق السلاسل النصية باستخدام الوحدة pyperclip تمتلك الوحدة pyperclip الدالتين copy()‎ و paste()‎ التي يمكنها إرسال واستقبال النص من حافظة نظام التشغيل الذي تستعمله. إذ يسهل عليك إرسال ناتج برنامجك إلى الحافظة أن تلصقه في رسالةٍ بريدية أو في محرر النصوص أو غيره من البرمجيات. تشغيل سكربتات بايثون خارج محرر Mu شغلنا كل سكربتات بايثون التي كتبناها حتى الآن باستخدام الصدفة التفاعلية ومحرر الشيفرات Mu؛ لكننا لسنا بحاجة إلى فتح محرر Mu في كل مرة نريد فيها تنفيذ سكربتات بايثون التي كتبناها. لحسن الحظ هنالك طرائق تسهل علينا تشغيل سكربتات بايثون، لكن هذه الطرائق تختلف من نظام ويندوز إلى MacOS ولينكس، وهي مشروحة في المقال الأول من هذه السلسلة، لذا أنصحك بالانتقال إلى المقال الأول لتعرف كيف تشغل سكربتات بايثون بسهولة على نظامك، وكيف تمرر خيارات سطر الأوامر command line arguments إليها، لاحظ أنك لا تستطيع تمرير خيارات سطر الأوامر باستخدام محرر Mu. الوحدة pyperclip غير مضمنة في بايثون، لذا عليك تثبيتها باتباع التعليمات المذكورة في المقال الأول من السلسلة أيضًا. يمكنك أن تدخل ما يلي في الصدفية التفاعلية بعد تثبيت الوحدة pyperclip: >>> import pyperclip >>> pyperclip.copy('Hello, world!') >>> pyperclip.paste() 'Hello, world!' إذا غيّر أحد البرامج محتويات الحافظة فستعيدها الدالة paste()‎، فانسخ مثلًا عبارةً من المتصفح أو من محرر الشيفرات ثم استدعِ الدالة paste()‎: >>> pyperclip.paste() 'For example, if I copied this sentence to the clipboard and then called paste(), it would look like this:' مشروع: حافظة فيها رسائل تلقائية إذا سبق وأن رددت على عدد كبير من رسائل البريد الإلكتروني بعبارات متشابهة فمن المرجح أنك قد قضيت وقتًا طويلًا في الكتابة على الحاسوب، ومن المرجح أن لديك ملف نصي فيه تلك العبارات ليسهل عليك نسخها ولصقها من الحافظة؛ لكن يمكن لحافظة نظام تشغيلك الاحتفاظ برسالة واحدة فقط في آن واحد وهذا ليس مريحًا في العمل. لنحاول سويةً تسهيل هذه المهمة بكتابة برنامج يخزن عددًا من العبارات. الخطوة 1: تصميم البرنامج وبنى المعطيات نرغب أن نشغل هذا البرنامج من سطر الأوامر مع تمرير وسيط إليه الذي يحتوي على كلمة مفتاحية واحدة مثل «موافق» agree أو «مشغول» busy. وستُنسَخ الرسالة المرتبطة بتلك الكلمة المفتاحية إلى الحافظة لكي يستطيع المستخدم لصقها مباشرةً في الردود، وبهذا يمكن أن نستعمل عبارات طويلة دون الحاجة إلى إعادة كتابتها كل مرة. مشاريع الفصول هذا هو أول «مشروع» في هذه السلسلة ومن الآن فصاعدًا ستجد في كل مقال عددًا من المشاريع التي تستعمل المفاهيم المشروحة في ذاك المقال وتبدأ هذه المشاريع من الصفر حتى الحصول على برنامج يعمل تمامًا، وأنصحك وبشدة أن تطبق أولًا بأول ولا تكتفي بالقراءة فقط. افتح نافذة محرر جديدة واحفظ البرنامج الآتي باسم mclip.py، ستحتاج إلى بدء البرنامج مع الرمز ‎#!‎ (الذي يسمى shebang، راجع المقال الأول من هذه السلسلة)، ثم كتابة تعليق يشرح باختصار ما يفعله البرنامج. لمّا كنّا نريد أن نربط كل جملة نصية بمفتاح، فمن المنطقي أن نخزنها في قاموس، والذي سيكون هو بنية المعطيات الأساسية في برنامجنا: #! python3 # mclip.py - A multi-clipboard program. TEXT = {'agree': """Yes, I agree. That sounds fine to me.""", 'busy': """Sorry, can we do this later this week or next week?""", 'upsell': """Would you consider making this a monthly donation?"""} الخطوة 2: التعامل مع وسائط سطر الأوامر تخزن الوسائط الممررة من سطر الأوامر command line arguments في المتغير sys.argv (راجع المقال الأول من هذه السلسلة لمزيدٍ من المعلومات حول استخدام وسائط سطر الأوامر في نظامك). يجب أن يكون أول عنصر في القائمة sys.argv هو سلسلة نصية تمثل اسم الملف 'mclip.py' أما العنصر الثاني في القائمة فهو قيمة الوسيط الأول الممرر عبر سطر الأوامر. سيمثل الوسيط الأول قيمة مفتاح العبارة التي نريد نسخها، ولأننا نريد إجبار المستخدم على تمرير وسيط في سطر الأوامر، فستعرض رسالةً إلى المستخدم إن نسي توفيره للبرنامج (إذا كانت القائمة sys.argv تحتوي على أقل من قيمتين): #! python3 # mclip.py - A multi-clipboard program. TEXT = {'agree': """Yes, I agree. That sounds fine to me.""", 'busy': """Sorry, can we do this later this week or next week?""", 'upsell': """Would you consider making this a monthly donation?"""} import sys if len(sys.argv) < 2: print('Usage: python mclip.py [keyphrase] - copy phrase text') sys.exit() keyphrase = sys.argv[1] # أول وسيط في سطر الأوامر هو مفتاح العبارة التي نريد نسخها الخطوة 3: نسخ العبارة الصحيحة أصبح لدينا مفتاح العبارة مخزنًا في المتغير keyphrase، لذا ستحتاج إلى التحقق إن كان هذا المفتاح موجودًا في القاموس TEXT، فإن كان موجودًا فسننسخ العبارة المرتبطة بهذا المفتاح إلى الحافظة باستخدام الدالة pyperclip.copy()‎، ولمّا كنّا نستعمل الوحدة pyperclip فسنحتاج إلى استيرادها أيضًا. لاحظ أننا لسنا بحاجة إلى إنشاء المتغير keyphrase، إذ نستطيع استخدام sys.argv[1]‎ في أي مكان استعملنا فيه keyphrase لكن وجود متغير باسم keyphrase سيسهل قراءة الشيفرة كثيرًا: #! python3 # mclip.py - A multi-clipboard program. TEXT = {'agree': """Yes, I agree. That sounds fine to me.""", 'busy': """Sorry, can we do this later this week or next week?""", 'upsell': """Would you consider making this a monthly donation?"""} import sys, pyperclip if len(sys.argv) < 2: print('Usage: py mclip.py [keyphrase] - copy phrase text') sys.exit() keyphrase = sys.argv[1] # first command line arg is the keyphrase if keyphrase in TEXT: pyperclip.copy(TEXT[keyphrase]) print('Text for ' + keyphrase + ' copied to clipboard.') else: print('There is no text for ' + keyphrase) ستبحث الشيفرة في القاموس TEXT عن المفتاح، وإن وجدت فسنحصل على العبارة المرتبطة بذاك المفتاح، ثم ننسخها إلى الحافظة، ونطبع رسالة فيها العبارة المنسوخة، وإن لم نعثر على المفتاح فستظهر رسالة للمستخدم تخبره بعدم وجود هكذا مفتاح. السكربت السابق كامل، ويمكننا اتباع التعليمات الموجودة في المقال الأول لتشغيل البرامج السطرية بسهولة، وأصبح لدينا الآن طريقة سريعة لنسخ الرسائل الطويلة إلى الحافظة بسهولة. لا تنسَ أن تعدل قيمة القاموس TEXT في كل مرة تحتاج فيها إلى إضافة عبارة جديدة. إذا كنت على نظام ويندوز فيمكنك إنشاء ملف دفعي batch file لتشغيل البرنامج باستخدام نافذة تشغيل البرامج Run (بالضغط على Win+R). أدخل ما يلي في محرر النصوص واحفظه باسم mclip.bat في مجلد C:\Windows: @py.exe C:\path_to_file\mclip.py %* @pause يمكننا بعد إنشاء الملف الدفعي أن نشغل برنامجنا بالضغط على Win+R ثم كتابة mclip key_phrase. مشروع: إضافة قائمة منقطة إلى ويكيبيديا حينما تعدل مقالًا في ويكيبيديا، فيمكنك إنشاء قائمة منقطة بوضع كل عنصر من عناصر القائمة في سطر خاص به ووضع نجمة قبله؛ لكن لنقل أن لدينا قائمة طويلة جدًا من العناصر التي تريد تحويلها إلى قائمة منقطة، هنا يمكنك أن تقضي بعض الوقت بإضافة رمز النجمة في بداية كل سطر يدويًا أو تكتب سكربتًا يؤتمت هذه العملية. يأخذ السكربت bulletPointAdder.py النص الموجود في الحافظة ويضيف نجمة وفراغًا إلى بداية كل سطر فيه، ثم يحفظ النص الناتج إلى الحافظة. فمثلًا إذا نسخت النص الآتي من صفحة ويكيبيديا المسماة «قائمة القوائم» List of Lists of Lists إلى الحافظة: Lists of animals Lists of aquarium life Lists of biologists by author abbreviation Lists of cultivars ثم شغلت البرنامج bulletPointAdder.py فستصبح محتويات الحافظة كما يلي: * Lists of animals * Lists of aquarium life * Lists of biologists by author abbreviation * Lists of cultivars ونستطيع الآن لصق النص الناتج إلى ويكيبيديا كقائمة منقطة. الخطوة 1: النسخ واللصق من وإلى الحافظة نريد من برنامج bulletPointAdder.py أن يفعل ما يلي: يأخذ النص الموجود في الحافظة يجري عليه عمليات يحفظ الناتج في الحافظة الخطوة الثانية صعبة بعض الشيء، لكن الخطوات 1 و 3 سهلة جدًا: كل ما علينا فعله هو استدعاء الدالتين pyperclip.copy()‎ و pyperclip.paste()‎. لننشِئ برنامجًا ينفذ الخطوات 1 و 3: #! python3 # bulletPointAdder.py - Adds Wikipedia bullet points to the start # of each line of text on the clipboard. import pyperclip text = pyperclip.paste() # TODO: فصل الأسطر وإضافة رمز النجمة pyperclip.copy(text) التعليق الذي يبدأ بالكلمة TODO هو تذكير لنا أن علينا إكمال هذا الجزء من البرنامج، لذا ستكون الخطوة القادمة هي برمجة هذا الجزء. الخطوة 2: فصل الأسطر وإضافة النجمة ناتج استدعاء pyperclip.paste()‎ يعيد النص الموجود في الحافظة كسلسة نصية واحدة، إذ سيبدو مثال «قائمة القوائم» الذي نسخناه على الشكل الآتي: 'Lists of animals\nLists of aquarium life\nLists of biologists by author abbreviation\nLists of cultivars' يوجد المحرف ‎\n في السلسلة النصية السابقة لعرضها على عدة أسطر حين لصقها من الحافظة، لاحظ أن السلسلة النصية السابقة «متعددة» الأسطر لوجود محرف التهريب ‎\n. ستحتاج إلى إضافة نجمة إلى بداية كل سطر من الأسطر السابقة. يمكنك أن تكتب شيفرة تبحث عن المحرف ‎\n وتضيف نجمةً قبله، أو أن تستعمل التابع split()‎ لإعادة قائمة من السلاسل النصية يمثل كل عنصر فيها سطرًا من السلسلة النصية الأصلية، وبعد ذلك تضيف النجمة قبل كل عنصر: #! python3 # bulletPointAdder.py - Adds Wikipedia bullet points to the start # of each line of text on the clipboard. import pyperclip text = pyperclip.paste() # فصل الأسطر وإضافة النجمة lines = text.split('\n') for i in range(len(lines)): # المرور على جميع عناصر القائمة lines[i] = '* ' + lines[i] # إضافة نجمة وفراغ قبل كل عنصر pyperclip.copy(text) قسمنا النص في مكان السطر الجديد ليمثل كل عنصر في القائمة سطرًا واحدًا، وخزّنا الناتج في المتغير lines، ثم مررنا على عناصر القائمة lines، ولكل عنصر أضفنا نجمة وفراغًا في بداية السطر. أصبحت لدينا الآن قائمة باسم lines فيها عناصر القائمة وقبل كل عنصر رمزُ النجمة. الخطوة 3: دمج الأسطر المعدلة تحتوي القائمة lines على الأسطر المعدلة التي تبدأ بالنجمة، لكن الدالة pyperclip.copy()‎ تتعامل مع السلاسل النصية وليس مع القوائم، لذا نحتاج إلى تحويل القائمة إلى سلسلة نصية، وذلك بجمع عناصر مع بعضها بعضًا عبر التابع join()‎: #! python3 # bulletPointAdder.py - Adds Wikipedia bullet points to the start # of each line of text on the clipboard. import pyperclip text = pyperclip.paste() # فصل الأسطر وإضافة النجمة lines = text.split('\n') for i in range(len(lines)): # المرور على جميع عناصر القائمة lines[i] = '* ' + lines[i] # إضافة نجمة وفراغ قبل كل عنصر text = '\n'.join(lines) pyperclip.copy(text) حين تشغيل البرنامج السابق فسيبدل محتويات الحافظة ويضيف نجمةً قبل كل سطر موجود فيها. أصبح البرنامج جاهزًا للتجربة. حتى لو لم تكن بحاجة إلى أتمتة هذه المهمة البسيطة (فهنالك محرر مرئي لويكيبيديا مثلًا) لكن قد تستفيد من برامج مشابهة لأتمتة عمليات بسيطة لمعالجة النصوص، مثل إزالة الفراغات، أو تحويل حالة الأحرف، أو أيًّا كان غرضك من تعديل محتويات الحافظة. برنامج قصير: اللغة السرية " أين الحلقة السابقة؟ يحتاج المثال إلى إعادة صياغة كليًا" هنالك لغة سرية يلعب الأطفال ويستعملونها اسمها Pig Latin، وهي تحريف للكلمات الإنكليزية بقواعد بسيطة. فلو بدأت الكلمة بحرف متحرك فتضاف الكلمة yay إلى نهايتها، وإذا بدأت الكلمة بحرف ساكن أو تركيب ساكن (مثل ch أو gr) فسينقل ذاك الساكن إلى نهاية البرنامج مع إلحاق ay. لنكتب برنامجًا للتحويل إلى اللغة السرية يطبع شيئًا يشبه المثال الآتي: Enter the English message to translate into Pig Latin: My name is AL SWEIGART and I am 4,000 years old. Ymay amenay isyay ALYAY EIGARTSWAY andyay Iyay amyay 4,000 yearsyay oldyay. يستعمل برنامجنا التوابع التي تعرفنا عليها في هذا المقال احفظ السكربت الآتي في ملف باسم pigLat.py: # English to Pig Latin print('Enter the English message to translate into Pig Latin:') message = input() VOWELS = ('a', 'e', 'i', 'o', 'u', 'y') pigLatin = [] # قائمة الكلمات في اللغة السرية for word in message.split(): # فصل الكلمات التي لا تبدأ بأحرف prefixNonLetters = '' while len(word) > 0 and not word[0].isalpha(): prefixNonLetters += word[0] word = word[1:] if len(word) == 0: pigLatin.append(prefixNonLetters) continue # فصل الكلمات التي لا تنتهي بحرف suffixNonLetters = '' while not word[-1].isalpha(): suffixNonLetters += word[-1] word = word[:-1] # تذكر حالة الأحرف wasUpper = word.isupper() wasTitle = word.istitle() word = word.lower() # تحويل الكلمة إلى الحالة الصغيرة لتحويلها # فصل الأحرف الساكنة في بداية الكلمة prefixConsonants = '' while len(word) > 0 and not word[0] in VOWELS: prefixConsonants += word[0] word = word[1:] # إضافة اللاحقة الخاصة باللغة السرية إلى نهاية الكلمة if prefixConsonants != '': word += prefixConsonants + 'ay' else: word += 'yay' # إعادة الكلمة إلى حالتها الأصلية if wasUpper: word = word.upper() if wasTitle: word = word.title() # إضافة الرموز التي استخرجناها سابقًا pigLatin.append(prefixNonLetters + word + suffixNonLetters) # جمع الكلمات مع بعضها إلى سلسلة نصية print(' '.join(pigLatin)) لنلقِ نظرةً على الشيفرة من بدايتها: # English to Pig Latin print('Enter the English message to translate into Pig Latin:') message = input() VOWELS = ('a', 'e', 'i', 'o', 'u', 'y') طلبنا في البداية من المستخدم أن يدخل نصًا إنكليزيًا لتحويله إلى اللغة السرية، وأنشأنا أيضًا ثابتًا يحتوي على الأحرف الصوتية في الإنكليزية (بالإضافة إلى الحرف y) في صف tuple من السلاسل النصية. أنشأنا بعد ذلك المتغير pigLatin لتخزين الكلمات بعد تحويلها إلى اللغة السرية: pigLatin = [] # قائمة الكلمات في اللغة السرية for word in message.split(): # فصل الكلمات التي لا تبدأ بأحرف prefixNonLetters = '' while len(word) > 0 and not word[0].isalpha(): prefixNonLetters += word[0] word = word[1:] if len(word) == 0: pigLatin.append(prefixNonLetters) continue نريد أن نعالج كل كلمة بمفردها، لذا استعملنا التابع message.split()‎ للحصول على قائمة بالكلمات، فالسلسلة النصية ‎'My name is AL SWEIGART and I am 4,000 years old.'‎ مع التابع split()‎ سيعيد الناتج. ‎['My', 'name', 'is', 'AL', 'SWEIGART', 'and', 'I', 'am', '4,000', 'years', 'old.']‎. سنحتاج إلى الاحتفاظ بأي رموز في بداية ونهاية كل كلمة، فلو كانت لدينا الكلمة 'old.‎' فستحول إلى 'oldyay.‎' بدلًا من 'old.yay'. سنحتفظ بهذه الرموز في متغير باسم prefixNonLetters. # فصل الكلمات التي لا تنتهي بحرف suffixNonLetters = '' while not word[-1].isalpha(): suffixNonLetters += word[-1] word = word[:-1] سنبدأ حلقة تكرار تستدعي التابع isalpha()‎ على أول حرف من الكلمة لتتأكد إن كان علينا إزالة حرف من الكلمة وإضافته إلى نهاية السلسلة النصية prefixNonLetters، وإذا كانت الكلمة كلها تتألف من رموز أو أرقام مثل '4,000' فسنضيفها كما هي إلى القائمة pigLatin وننتقل إلى الكلمة التالية لنحولها إلى اللغة السرية. سنحتفظ بالرموز في نهاية السلسلة النصية word، وهذا يشبه الحلقة السابقة. نرغب أن يتذكر البرنامج حالة الأحرف للكلمة لكي نستطيع استعادتها بعد عملية التحويل: # تذكر حالة الأحرف wasUpper = word.isupper() wasTitle = word.istitle() word = word.lower() # تحويل الكلمة إلى الحالة الصغيرة لتحويلها سنستخدم الكلمة المخزنة في المتغير word بحالتها الصغيرة حتى نهاية دورة حلقة for. لتحويل إحدى الكلمات مثل sweigart إلى eigart-sway فسنتحتاج إلى إزالة جميع الأحرف الساكنة من بداية الكلمة word: # فصل الأحرف الساكنة في بداية الكلمة prefixConsonants = '' while len(word) > 0 and not word[0] in VOWELS: prefixConsonants += word[0] word = word[1:] استخدمنا حلقة تكرار تشبه الحلقة التي أزالت الرموز من بداية الكلمة word، لكننا الآن نزيد الأحرف الساكنة ونخزنها في متغير باسم prefixConsonants. إذا لم يبقَ أي حرف ساكن في بداية الكلمة، فهذا يعني أنها أصبحت كلها في المتغير prefixConsonants، ويمكننا الآن جمع قيمة ذاك المتغير مع السلسلة النصية 'ay' في نهاية المتغير word. أما خلاف ذلك فهذا يعني أن word تبدأ بحرف صوتي، وسنحتاج إلى إضافة 'yay': # إضافة اللاحقة الخاصة باللغة السرية إلى نهاية الكلمة if prefixConsonants != '': word += prefixConsonants + 'ay' else: word += 'yay' تذكر أننا جعلنا الكلمة بحالة الأحرف الصغيرة word = word.lower()‎. أما إذا كانت القيمة word بحالة الأحرف الكبيرة أو نسق العناوين Title case فعلينا تحويلها إلى حالتها الأصلية: # إعادة الكلمة إلى حالتها الأصلية if wasUpper: word = word.upper() if wasTitle: word = word.title() وفي نهاية دورة حلقة for سنضيف الكلمة مع أي رموز سابقة أو لاحقة إلى القائمة pigLatin: # إضافة الرموز التي استخرجناها سابقًا pigLatin.append(prefixNonLetters + word + suffixNonLetters) # جمع الكلمات مع بعضها إلى سلسلة نصية print(' '.join(pigLatin)) بعد نهاية حلقة التكرار فسنجمع عناصر القائمة pigLatin مع بعضها باستخدام التابع join()‎، وستمرر سلسلة نصية واحدة إلى الدالة print()‎ لطباعة الجملة السرية. الخلاصة النصوص هي أكثر أنواع البيانات شيوعًا، وتأتي بايثون مع مجموعة من توابع معالجة النصوص المفيدة. ستستخدم الفهرسة والتقسيم وتوابع السلاسل النصية في كل برنامج بايثون تكتبه تقريبًا. صحيحٌ أن البرامج التي تكتبها الآن لا تبدو معقدة جدًا، فهي لا تحتوي على واجهات مستخدم رسومية فيها صور ونصوص ملونة، فكل ما نستعمله هو الدالة print()‎ لطباعة النصوص و input()‎ لقبول مدخلات المستخدم؛ لكن يستطيع المستخدم إدخال مجموعة كبيرة من النصوص عبر نسخها إلى الحافظة، مما يفيد كثيرًا في معالجة كميات كبيرة من النصوص. وصحيحٌ أن هذه البرامج لا تملك واجهات رسومية جميلة لكنها تفعل الكثير بجهدٍ قليل. طريقة أخرى لمعالجة كمية كبيرة من النصوص هي كتابة الملفات وقراءتها من نظام الملفات المحلي مباشرةً، وسنتعلم كيفية فعل ذلك في مقال لاحق. لقد شرحنا حتى الآن المفاهيم الأساسية في برمجة بايثون! ستتعلم مفاهيم جديدة في بقية هذه السلسلة، لكن يفترض أنك تعرف ما يكيفك لكتابة برامج مفيدة تستطيع عبرها أتمتة المهام. قد تظن أنك لا تمتلك المعرفة الكافية في بايثون لتنزيل صفحات الويب أو تحديث جداول البيانات أو إرسال الرسائل البريدية، لكن هنا يأتي دور الوحدات الخارجية في بايثون، التي توفر لك ما تحتاج إليه لفعل كل ذلك وأكثر منه، والتي سنتعلمها سويةً في المقالات القادمة. مشاريع تدريبية لكي تتدرب، اكتب برامج لتنفيذ المهام الآتية. طابع جداول اكتب دالةً اسمها printTable()‎ تقبل قائمةً فيها قوائم تحتوي على سلاسل نصية، وتعرضها في جدول منسق تكون فيه محاذاة الأعمدة إلى اليمين. افترض أن لكل القوائم الداخلية العدد نفسه من السلاسل النصية، فمثلًا: tableData = [['apples', 'oranges', 'cherries', 'banana'], ['Alice', 'Bob', 'Carol', 'David'], ['dogs', 'cats', 'moose', 'goose']] يجب أن تطبع الدالة printTable()‎ ما يلي: apples Alice dogs oranges Bob cats cherries Carol moose banana David goose تلميح: يجب أن تبحث عن أطول سلسلة نصية في كل قائمة داخلية، كي يتسع العمود لجميع السلاسل النصية. يمكنك تخزين العرض الأقصى لكل عمود في قائمة من الأرقام. يمكن أن تبدأ الدالة printTable()‎ بالسطر colWidths = [0] * len(tableData)‎ الذي سينشِئ قائمةً تحتوي على الرقم 0 يساوي عدد القوائم الداخلية الموجودة في القائمة tableData، وبالتالي ستخزن عرض أطول سلسلة نصية في tableData[0] في العنصر colWidths[0]‎، وعرض أطول سلسلة نصية في القائمة tableData[1] في العنصر colWidths[1] وهلم جرًا… ثم يمكنك الحصول على أكبر قيمة في القائمة colWidths لتعرف القيمة التي ستمررها كعرض إلى التابع rjust()‎. ترجمة -بتصرف- للفصل Manipulating Strings من كتاب Automate the Boring Stuff with Python. اقرأ أيضًا المقال السابق: القواميس وهيكلة البيانات في بايثون python القوائم Lists في لغة بايثون تهيئة بيئة العمل في بايثون Python
  3. سنشرح في هذا المقال نوع البيانات المسمى بالقاموس dictionary، الذي يوفر طريقة مرنة للوصول إلى البيانات وتنظيمها، ثم سنتعلم كيفية كتابة لعبة إكس-أو عبر دمج ما تعلمناه في المقالات السابقة من هذه السلسلة مع القواميس. نوع البيانات dictionary القواميس تشبه القوائم في أنها مجموعة قابلة للتعديل mutable من القيم، لكن بدلًا من وجود الفهارس الرقمية للعناصر كما في القوائم، يمكن استخدام مختلف أنواع البيانات في القواميس. نسمي المفاتيح في القواميس بالمفاتيح keys، وكل مفتاح مرتبط بقيمة، ونسمي ذلك زوجًا من المفاتيح والقيم key-value pair. نكتب القواميس في بايثون بإحاطها بقوسين مجعدين أو معقوصين {}: >>> myCat = {'size': 'fat', 'color': 'gray', 'disposition': 'loud'} السطر السابق يسند قاموسًا إلى المتغير myCat، ومفاتيح هذا القاموس هي 'size' و 'color' و 'disposition'، والقيم المرتبطة بتلك المفاتيح هي 'fat' و 'gray' و 'loud' على التوالي وبالترتيب. يمكنك الوصول إلى هذه القيم عبر مفاتيحها: >>> myCat['size'] 'fat' >>> 'My cat has ' + myCat['color'] + ' fur.' 'My cat has gray fur.' ما يزال بالإمكان استخدام الأعداد الصحيحة مفاتيحًا لقيم القواميس، لكن ليس من الضروري أن تبدأ من الصفر 0 ويمكنك استخدام أي رقم: >>> spam = {12345: 'Luggage Combination', 42: 'The Answer'} مقارنة القواميس والقوائم على خلاف القوائم، لا تكون القواميس مرتبةً، فأول عنصر في قائمة اسمها spam سيكون spam[0]‎، لكن لا يوجد «أول» عنصر في القاموس. سيكون ترتيب العناصر مهمًا في حال أردنا تحديد إن كانت قائمتان متساويتين، بينما لا يفرق ترتيب كتابة أزواج المفاتيح-القيم في القواميس: >>> spam = ['cats', 'dogs', 'moose'] >>> olive = ['dogs', 'moose', 'cats'] >>> spam == olive False >>> eggs = {'name': 'Zophie', 'species': 'cat', 'age': '8'} >>> steak = {'species': 'cat', 'age': '8', 'name': 'Zophie'} >>> eggs == steak True ولأن القواميس غير مرتبة، فلا يمكن تقسيمها كما في القوائم. محاولة الوصول إلى مفتاح غير موجود في قاموس ما سيؤدي إلى حدوث الخطأ KeyError، وهي تشبه رسالة الخطأ IndexError في القوائم حين محاولة الوصول إلى قيمة خارج مجال فهارس القائمة. لاحظ رسالة الخطأ الآتية التي تظهر لعدم وجود المفتاح 'color': >>> spam = {'name': 'Zophie', 'age': 7} >>> spam['color'] Traceback (most recent call last): File "<pyshell#1>", line 1, in <module> spam['color'] KeyError: 'color' وصحيحٌ أن القواميس غير مرتبة، لكن إمكانية استخدام أي قيمة تريدها للمفاتيح يسمح لنا بترتيب البيانات بطرائق رائعة! لنقل أننا نريد كتابة برنامج يخزن معلومات حول أعياد ميلاد أصدقائك، يمكنك أن تكتب الشيفرة الآتية birthdays.py التي تستخدم القواميس وتجعل أسماء أصدقائك مفاتيحًا للقيم: ➊ birthdays = {'Alice': 'Apr 1', 'Bob': 'Dec 12', 'Carol': 'Mar 4'} while True: print('Enter a name: (blank to quit)') name = input() if name == '': break ➋ if name in birthdays: ➌ print(birthdays[name] + ' is the birthday of ' + name) else: print('I do not have birthday information for ' + name) print('What is their birthday?') bday = input() ➍ birthdays[name] = bday print('Birthday database updated.') أنشأنا قاموسًا وخزناه في المتغير birthdays ➊، ثم تحققنا إن كان الاسم المدخل موجودًا في القاموس عبر الكلمة المحجوزة in ➋ كما كنا نفعل مع القوائم. إذا كان الاسم موجودًا في القاموس فيمكنك الوصول إلى القيمة المرتبطة به عبر استخدام الأقواس المربعة ➌، وإلّا فيمكنك إضافتها باستخدام صيغة الأقواس المربعة مع عامل الإسناد ➍: Enter a name: (blank to quit) Alice Apr 1 is the birthday of Alice Enter a name: (blank to quit) Eve I do not have birthday information for Eve What is their birthday? Dec 5 Birthday database updated. Enter a name: (blank to quit) Eve Dec 5 is the birthday of Eve Enter a name: (blank to quit) للأسف ستحذف كل المعلومات التي أدخلتها في هذا البرنامج حين انتهاء تنفيذه. ستتعلم كيفية حفظ الملفات على القرص في مقال لاحق من هذه السلسلة. القواميس المرتبة في بايثون 3.7 صحيح أن القواميس غير مرتبة ولا يوجد فيها عنصر «أول»، لكن القواميس في الإصدار 3.7 من بايثون وما يليه ستتذكر ترتيب أزواج القيم الموجودة فيها حين إنشاء متسلسل sequence منها. فمثلًا لاحظ أن ترتيب العناصر في القائمتين المنشأتين من القاموسين eggs و steak يطابق ترتيب إدخالها: >>> eggs = {'name': 'Zophie', 'species': 'cat', 'age': '8'} >>> list(eggs) ['name', 'species', 'age'] >>> steak = {'species': 'cat', 'age': '8', 'name': 'Zophie'} >>> list(steak) ['species', 'age', 'name'] ستبقى القواميس غير مرتبة، ولا يمكنك أن تصل إلى العناصر فيها عبر فهرس رقمي مثل eggs[0]‎ أو steak[2]‎، لا يجدر بك الاعتماد على هذا السلوك لأن بايثون لا تتذكر ترتيب إدخال العناصر في الإصدارات القيمة منها، فلاحظ المثال الآتي الذي لا يطابق ترتيب عناصر القاموس الناتج النهائي الترتيبَ الذي أدخلتها به، ناتج التنفيذ الآتي على إصدار بايثون 3.5: >>> spam = {} >>> spam['first key'] = 'value' >>> spam['second key'] = 'value' >>> spam['third key'] = 'value' >>> list(spam) ['first key', 'third key', 'second key'] التوابع keys()‎ و values()‎ و items()‎ هنالك ثلاثة توابع خاصة بالقواميس التي تعيد قيمًا شبيهة بالقوائم list-like من مفاتيح القواميس أو قيمها أو كلًا من المفاتيح والقيم معًا وهي التوابع keys()‎ و values()‎ و items()‎ بالترتيب. القيم المعادة من هذه التوابع ليست قوائم حقيقيةً، فلا يمكننا تعديلها وليس لها التابع append()‎، لكن أنواع البيانات المعادة (وهي dictkeys و dictvalues و dict_items بالترتيب) يمكن أن تستخدم في حلقات التكرار for: >>> spam = {'color': 'red', 'age': 42} >>> for v in spam.values(): ... print(v) red 42 ستمر حلقة for هنا على كل قيمة في القاموس spam، يمكن لحلقة for المرور على المفاتيح فقط، وعلى المفاتيح والقيم معًا: >>> for k in spam.keys(): ... print(k) color age >>> for i in spam.items(): ... print(i) ('color', 'red') ('age', 42) حين استخدامنا للتوابع keys()‎ و values()‎ و items()‎ فيمكن للحلقة for المرور على قيم المفاتيح أو قيم العناصر أو قيم أزواج المفتاح-القيمة على التوالي. لاحظ أن القيم المعادة من items()‎ هي صفوف tuples تحتوي على المفتاح ثم قيمته. إذا أردتَ قائمةً حقيقية من ناتج أحد تلك التوابع، فيمكننا تمرير القيمة الشبيهة بالقوائم إلى الدالة list()‎ كما يلي: >>> spam = {'color': 'red', 'age': 42} >>> spam.keys() dict_keys(['color', 'age']) >>> list(spam.keys()) ['color', 'age'] يأخذ السطر list(spam.keys())‎ القيمة ذات النوع dict_keys المعادة من التابع keys()‎ ويمررها إلى الدالة list()‎، والتي تعيد بدورها قائمةً فيها ['color', 'age']. يمكنك استخدام الإسناد المتعدد مع حلقة for لإسناد المفتاح والقيمة إلى متغيرات منفصلة: >>> spam = {'color': 'red', 'age': 42} >>> for k, v in spam.items(): ... print('Key: ' + k + ' Value: ' + str(v)) Key: age Value: 42 Key: color Value: red التحقق من وجود مفتاح أو قيمة في قاموس نتذكر من المقال السابق أن العاملين in و not in يمكن أن يستخدما للتحقق من وجود قيمة في قائمة. ويمكننا استخدام نفس العاملين للتحقق من وجود قيمة ما في مفتاح أو قيمة في قاموس: >>> spam = {'name': 'Zophie', 'age': 7} >>> 'name' in spam.keys() True >>> 'Zophie' in spam.values() True >>> 'color' in spam.keys() False >>> 'color' not in spam.keys() True >>> 'color' in spam False لاحظ أن كتابة ‎'color' in spam هي مطابقة لكتابة ‎'color' in spam.keys()‎. فإذا أردت التحقق من وجود (أو عدم وجود) مفتاح ما في قاموس فاستعمل الكلمة المحجوزة in (أو not in) مع القاموس نفسه. التابع get()‎ من الرتيب أن نتحقق من وجود مفتاح ما في القاموس قبل الوصول إلى القيمة المرتبطة به، لكن لحسن الحظ هنالك تابع باسم get()‎ يأخذ وسيطين: الأول هو المفتاح الذي نريد الوصول إلى قيمته، والثاني هو قيمة افتراضية ستعاد إن لم يكن المفتاح موجودًا: >>> picnicItems = {'apples': 5, 'cups': 2} >>> 'I am bringing ' + str(picnicItems.get('cups', 0)) + ' cups.' 'I am bringing 2 cups.' >>> 'I am bringing ' + str(picnicItems.get('eggs', 0)) + ' eggs.' 'I am bringing 0 eggs.' ولعدم وجود المفتاح 'eggs' في القاموس picnicItems فستعاد القيمة 0 من التابع get()‎، وإن لم نستعمل التابع get()‎ في المثال السابق فستظهر رسالة خطأ كما يلي: >>> picnicItems = {'apples': 5, 'cups': 2} >>> 'I am bringing ' + str(picnicItems['eggs']) + ' eggs.' Traceback (most recent call last): File "<pyshell#34>", line 1, in <module> 'I am bringing ' + str(picnicItems['eggs']) + ' eggs.' KeyError: 'eggs' التابع setdefault()‎ الحاجة إلى ضبط قيمة في القاموس مرتبطة بمفتاح معين إن لم يكن ذاك المفتاح موجودًا مسبقًا هو أمرٌ شائع، وتكون الشيفرة بالشكل الآتي: spam = {'name': 'Pooka', 'age': 5} if 'color' not in spam: spam['color'] = 'black' يوفر التابع setdefault()‎ طريقة أسهل لفعل ذلك بسطر برمجي وحيد، فأول وسيط يمرر إلى التابع هو المفتاح الذي سنتحقق من وجوده، والوسيط الثاني هو القيمة التي ستُضبَط إن لم يكن المفتاح موجودًا. إذا كان المفتاح موجودًا فسيعيد التابع setdefault()‎ قيمة ذاك المفتاح: >>> spam = {'name': 'Pooka', 'age': 5} >>> spam.setdefault('color', 'black') 'black' >>> spam {'color': 'black', 'age': 5, 'name': 'Pooka'} >>> spam.setdefault('color', 'white') 'black' >>> spam {'color': 'black', 'age': 5, 'name': 'Pooka'} حينما استدعينا التابع setdefault()‎ أول مرة، فتغيرت قيمة القاموس spam إلى ‎{'color': 'black', 'age': 5, 'name': 'Pooka'}‎، وسيعيد التابع setdefault()‎ القيمة 'black' لأنها القيمة المضبوطة للمفتاح 'color' حاليًا. لكن حين استدعاء spam.setdefault('color', 'white')‎ فإن القيمة لن تتغير إلى 'white' لأن القاموس spam يحتوي على مفتاح باسم 'color'. التابع setdefault()‎ هو اختصار جميل للتأكد من وجود مفتاح معين وضبط قيمته. هذا مثال عن برنامج يعدّ عدد مرات وجود كل حرف في سلسلة نصية. احفظ المثال الآتي باسم characterCount.py: message = 'It was a bright cold day in April, and the clocks were striking thirteen.' count = {} for character in message: ➊ count.setdefault(character, 0) ➋ count[character] = count[character] + 1 print(count) سيمر البرنامج على كل محرف في السلسلة النصية المخزنة في المتغير message، ويعد كم مرة يظهر فيها كل محرف. استدعاء الدالة setdefault()‎ ➊ سيضمن وجود المفتاح في القاموس count ويضبط قيمته الافتراضية إلى 0، وبالتالي لا يرمي البرنامج الخطأ KeyError حين تنفيذ التعبير البرمجي count[character] = count[character] + 1 ➋. سيبدو الناتج كما يلي: {' ': 13, ',': 1, '.': 1, 'A': 1, 'I': 1, 'a': 4, 'c': 3, 'b': 1, 'e': 5, 'd': 3, 'g': 2, 'i': 6, 'h': 3, 'k': 2, 'l': 3, 'o': 2, 'n': 4, 'p': 1, 's': 3, 'r': 5, 't': 6, 'w': 2, 'y': 1} سترى من الناتج السابق أن الحرف c الصغير مكرر 3 مرات، بينما الفراغ مكرر 13 مرة، والحرف A الكبير يظهر مرة واحدة. سيعمل البرنامج السابق على جميع السلاسل النصية بغض النظر عن محتويات المتغير message حتى لو كان يحتوي على مليون حرف! تجميل الطباعة إذا استوردت الوحدة pprint في برامجك، فيمكنك الوصول إلى الدالتين pprint()‎ و pformat()‎ التي «تجمل طباعة» pretty print قيم القواميس، ستستفيد من هذه الدوال إن أردت عرض قيم القواميس عرضًا أجمل من طريقة عرض الدالة print()‎، لنعدل المثال السابق: import pprint message = 'It was a bright cold day in April, and the clocks were striking thirteen.' count = {} for character in message: count.setdefault(character, 0) count[character] = count[character] + 1 pprint.pprint(count) سيظهر لنا الناتج الجميل الآتي: {' ': 13, ',': 1, '.': 1, 'A': 1, 'I': 1, --snip-- 't': 6, 'w': 2, 'y': 1} سنستفيد فعليًا من الدالة pprint.pprint()‎ عندما يحتوي القاموس على قوائم أو قواميس متشعبة داخله nested. إذا أردت الحصول على قيمة النص المجمّل بدلًا من طباعته على الشاشة مباشرةً، فاستدعِ الدالة pprint.pformat()‎. السطران الآتيان متكافئان تمامًا: pprint.pprint(someDictionaryValue) print(pprint.pformat(someDictionaryValue)) استخدام بنى المعطيات لنمذجة عناصر حقيقية كان بإمكاننا لعب الشطرنج مع شخص آخر عن بعد قليل ظهور الإنترنت، فكان يعد كل لاعب رقعة الشطرنج في منزله، ثم يبادلان الرسائل البريدية يصف كل منهما خطوته، ولكي يستطيعوا فعل ذلك كان لاعبو الشطرنج بحاجة إلى وصف حالة رقعة الشطرنج والحركات التي يجريها وصفًا لا لبس فيه. تمثل الفراغات في رقعة الشطرنج في التأشير الجبري Algebraic chess notation بإحداثيات تتألف من حرف ورقم كما في الشكل 5-1. الشكل 5-1: إحداثيات رقعة الشطرنج في التأشير الجبري تُعرّف قطع الشطرنج بالأحرف: K للملك king، و Q للملكة queen (يسميها البعض بالوزير)، و R للقلعة rook، و B للفيل bishop، و N للحصان knight. أما الجنود فلا رمز لهم. يكون وصف الحركات متألفًا من الحرف الذي يمثل القطعة، وإحداثيات الوجهة. وزوج من تلك الحركات يصف ما يحدث في دورٍ واحد (بفرض أن صاحب اللون الأبيض يبدأ أولًا)؛ فمثلًا التأشير 2‎. Nf3 Nc6 يعني أن الأبيض حرك الحصان إلى f3 والأسود حرك الحصان إلى c6 في الدور الثاني من اللعبة. هنالك المزيد من القواعد للتأشير الجبري للشطرنج، لكن الفكرة التي أحاول إيصالها هي أننا نستطيع وصف لعبة الشطرنج دون الحاجة إلى أن نكون أمام رقعة، ويمكن أن يكون خصمك في الطرف الثاني من الكوكب، وإذا كانت لديك ذاكرة ومخيلة جيدة فلا تحتاج إلى رقعة شطرنج حقيقية من الأساس: فيمكنك أن تقرأ الحركات من الرسائل البريدية وتحدِّث الرقعة الموجودة في مخيلتك! تمتلك الحواسيب ذواكر رائعة، فيمكن أن يخزن الحاسوب مليارات السلاسل النصية من الشكل ‎'2. Nf3 Nc6'‎، وبهذا يمكن أن تلعب الحواسيب الشطرنج دون لوحة حقيقية؛ فما تفعله الحواسيب هو نمذجة البيانات لتمثيل رقعة الشطرنج، ويمكنك كتابة شيفرة تفعل ذلك بنفسك. هنا تلعب القوائم والقواميس دورها، فمثلًا القاموس ‎{'1h': 'bking', '6c': 'wqueen', '2g': 'bbishop', '5h': 'bqueen', '3e': 'wking'}‎ يمثل الرقعة في الشكل 5-2: الشكل 5-2: رقعة شطرنج منمذجة وفق قيمة قاموس لكن لمثالنا القادمة سنستعمل لعبة أسهل وأبسط من الشطرنج وهي لعبة إكس-أو. لعبة إكس-أو لعبة إكس-أو (تسمى بالإنكليزية tic-tac-toe) تشبه رمز # كبير فيه 9 خانات يمكن أن تكون قيمها X أو O أو أن تكون فارغة. لتمثيل هذه الرقعة بقاموس، فيجب أن نسند لكل خانة زوجًا من المفتاح-القيمة كما في الشكل 5-3. الشكل 5-3: خانات رقعة إكس-أو مع المفاتيح الموافقة لها يمكنك استخدام القيم النصية لتمثيل ما هو موجود في كل خانة في الرقعة: 'x' أو 'o' أو ' ' (فراغ)، وبالتالي نحتاج إلى تسع سلاسل نصية، يمكنك استخدام قاموس من القيم لهذا الأمر، فالقيمة النصية المرتبطة مع المفتاح 'top-R' تمثل القيمة في الركن العلوي الأيمن، والسلسل النصية المرتبطة مع المفتاح 'low-L' تمثل الركن السفلي الأيسر، والسلسلة النصية المرتبطة مع المفتاح 'mid-m' تمثل المنتصف، وهلم جرًا للبقية. هذا القاموس هو بنية معطيات تمثل رقعة لعبة إكس-أو، ولنخزن هذا القاموس في متغير باسم theBoard، ولنحفظ الشيفرة الآتية في ملف باسم ticTacToe.py: theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ', 'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ', 'low-L': ' ', 'low-M': ' ', 'low-R': ' '} بنية المعطيات المخزنة في المتغير theBoard تمثل رقعة إكس-أو الموضحة في الشكل 5-4. الشكل 5-4: لوحة إكس-أو فارغة ولما كانت قيمة كل مفتاح في القاموس theBoard هي فراغ واحد فيمثل ذاك القاموس رقعةً فارغةً تمامًا. وإذا بدأت اللاعب X واختار الخانة في المنتصف تمامًا فسيصبح القاموس الذي يمثل الرقعة على الشكل: theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ', 'mid-L': ' ', 'mid-M': 'X', 'mid-R': ' ', 'low-L': ' ', 'low-M': ' ', 'low-R': ' '} أصبحت بنية المعطيات theBoard تمثل الرقعة الموضحة في الشكل 5-5. الشكل 5-5: الحركة الأولى وتكون رقعة ربح فيها اللاعب O بوضع الشكل O في الصف العلوي كما يلي: theBoard = {'top-L': 'O', 'top-M': 'O', 'top-R': 'O', 'mid-L': 'X', 'mid-M': 'X', 'mid-R': ' ', 'low-L': ' ', 'low-M': ' ', 'low-R': 'X'} وهي ممثلة في الشكل 5-6. الشكل 5-6: ربح اللاعب O وبالتأكيد لا يستطيع أن يرى اللاعب إلا ما يطبع على الشاشة أمامه، ولا يعرف محتويات المتغيرات، فلننشئ دالةً تطبع القاموس الذي يحتوي على الرقعة على الشاشة. أضف ما يلي إلى ملف ticTacToe.py: theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ', 'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ', 'low-L': ' ', 'low-M': ' ', 'low-R': ' '} def printBoard(board): print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R']) print('-+-+-') print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R']) print('-+-+-') print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R']) printBoard(theBoard) حينما تشغل هذا البرنامج فستطبع رقعة إكس-أو فارغة: | | -+-+- | | -+-+- | | يمكن أن تتوالى الدالة printBoard()‎ أي بنية معطيات تمثل رقعة إكس-أو تمررها إليها، جرب تغيير الشيفرة إلى ما يلي: theBoard = {'top-L': 'O', 'top-M': 'O', 'top-R': 'O', 'mid-L': 'X', 'mid-M': 'X', 'mid-R': ' ', 'low-L': ' ', 'low-M': ' ', 'low-R': 'X'} def printBoard(board): print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R']) print('-+-+-') print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R']) print('-+-+-') print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R']) printBoard(theBoard) ستعرض الرقعة الآتية على الشاشة: O|O|O -+-+- X|X| -+-+- | |X ولأنك أنشأت بنية معطيات تمثل لوحة إكس-أو وكتبت الشيفرة في printBoard()‎ التي تفسر بنية المعطيات وتظهر الرقعة، فأنت كتبت برنامجًا «ينمذج» models رقعة إكس-أو. كان بإمكانك تنظيم البيانات في بنية المعطيات بطريقة مختلفة، فمثلًا يمكنك استخدام المفتاح 'TOP-LEFT' بدلًا من 'top-L'، لكن طالما كانت شيفرتك تعمل مع بنية المعطيات التي لديك، فأنت كتبت عملية النمذجة بشكل صحيح. فمثلًا تتوقع الدالة printBoard()‎ أن بنية المعطيات التي تمثل الرقعة هي قاموس فيه مفاتيح لجميع الخانات التسع، لكن إن كان في قاموسك مفتاحٌ ناقص وليكن 'mid-L' فلن يعمل برنامجك: O|O|O -+-+- Traceback (most recent call last): File "ticTacToe.py", line 10, in <module> printBoard(theBoard) File "ticTacToe.py", line 6, in printBoard print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R']) KeyError: 'mid-L' لنضف الآن الشيفرة التي تسمح للاعبين بإدخال حركاتهم. لنعدل برنامجنا ليبدو كما يلي: theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ', 'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ', 'low-L': ' ', 'low-M': ' ', 'low-R': ' '} def printBoard(board): print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R']) print('-+-+-') print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R']) print('-+-+-') print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R']) turn = 'X' for i in range(9): ➊ printBoard(theBoard) print('Turn for ' + turn + '. Move on which space?') ➋ move = input() ➌ theBoard[move] = turn ➍ if turn == 'X': turn = 'O' else: turn = 'X' printBoard(theBoard) الشيفرة الجديدة تطبع اللوحة في بداية كل دور ➊ ثم تطلب المدخلات من اللاعب الحالي ➋ ثم تحدث الرقعة وفقًا لذلك ➌ ثم تبدل اللاعب الحالي ➍ قبل الانتقال إلى الدور القادم. | | -+-+- | | -+-+- | | Turn for X. Move on which space? mid-M | | -+-+- |X| -+-+- | | --snip-- O|O|X -+-+- X|X|O -+-+- O| |X Turn for X. Move on which space? low-M O|O|X -+-+- X|X|O -+-+- O|X|X صحيحٌ أن البرنامج ليس لعبة إكس-أو كاملة، فلن يتحقق إن ربح أحد اللاعبين مثلًا؛ لكنه كافٍ لمعرفة كيفية استخدام بنى المعطيات في برامج حقيقية. القواميس والقوائم المتشعبة نمذجة لوحة إكس-أو هو أمر سهل: تحتاج اللوحة إلى قاموس فيه 9 مفاتيح تمثل خاناتها. لكن إن أردت نموذج أمور أكثر تعقيدًا فستجد أنك تحتاج إلى القواميس والقوائم التي تحتوي على قواميس وقوائم أخرى داخلها. تناسب القوائم تخزين سلسلة مرتبة من القيم، بينما تفيد القواميس بتخزين قواميس التي ترتبط فيها المفاتيح مع القيم. هذا مثال يستعمل قاموسًا يحتوي على قواميس داخله فيها الأغراض التي أتى بها الضيوف إلى الرحلة. يمكن أن تقرأ الدالة totalBrought()‎ بنية المعطيات وتحسب العدد الكلي للعناصر المجلوبة من كل الضيوف: allGuests = {'Alice': {'apples': 5, 'pretzels': 12}, 'Bob': {'steak sandwiches': 3, 'apples': 2}, 'Carol': {'cups': 3, 'apple pies': 1}} def totalBrought(guests, item): numBrought = 0 ➊ for k, v in guests.items(): ➋ numBrought = numBrought + v.get(item, 0) return numBrought print('Number of things being brought:') print(' - Apples ' + str(totalBrought(allGuests, 'apples'))) print(' - Cups ' + str(totalBrought(allGuests, 'cups'))) print(' - Cakes ' + str(totalBrought(allGuests, 'cakes'))) print(' - Steak Sandwiches ' + str(totalBrought(allGuests, 'steak sandwiches'))) print(' - Apple Pies ' + str(totalBrought(allGuests, 'apple pies'))) داخل الدالة totalBrought()‎ هنالك حلقة for تدور على أزواج مفتاح-قيمة في المتغير guests ➊، وسنسند داخل الحلقة اسم كل ضيف إلى المتغير k، وسنسند القاموس الذي يحتوي على قائمة الأغراض التي سيجلبها معه إلى الرحلة إلى المتغير v. إذا كان أحد المعامل item موجودة في القاموس، فستضاف قيمته (كمية الأغراض المجلوبة) إلى المتغير numBrought ➋، لكن إذا لم يكن المفتاح موجودًا فيسعيد التابع get()‎ القيمة 0 لإضافتها إلى numBrought. سيكون ناتج تنفيذ البرنامج كما يلي: Number of things being brought: - Apples 7 - Cups 3 - Cakes 0 - Steak Sandwiches 3 - Apple Pies 1 قد يبدو لك أن حالة الاستخدام السابقة بسيطة ولا حاجة إلى نمذجتها، لكن فكر أن الدالة totalBrought()‎ يمكن أن تتعامل بسهولة مع قاموس يحتوي على آلاف الضيوف، وكل ضيف يجلب آلاف العناصر، وتنظيم هذه المعلومات في بنية معطيات واضحة ووجود الدالة totalBrought()‎ سيوفر عليك وقتًا كثيرًا. يمكنك أن تنمذج العناصر في بنى المعطيات بالطريقة التي تراها مناسبة، لطالما كانت بقية الشيفرة في برنامج قادرةً على التعامل مع بنية المعطيات المنشأة. حينما تبدأ البرمجة فلا تقلق أن تنمذج البيانات بالطريقة «الصحيحة»، فكلما ازدادت خبرتك أصبحت تخطر ببالك طرائق أكثر فاعلية وكفاءة للنمذجة، لكن أهم ما في الأمر أن تعمل بنية المعطيات مع احتياجات برنامجك. الخلاصة يمكن أن تحتوي القوائم والقواميس على عدّة قيم، بما فيها قوائم وقواميس أخرى. القواميس مفيدة لأنك تستطيع ربط مفتاح معين مع قيمة، على عكس القوائم التي هي سلسلة من القيم المرتبة. يمكن الوصول إلى القيم داخل القاموس عبر استخدام الأقواس المربعة كما في القوائم، لكن بدلًا من استخدام فهرس رقمي فيمكن أن تكون المفاتيح في القواميس من مختلف القيم سواءً كانت أعدادًا صحيحةً أو أعدادًا عشريةً أو سلاسل نصية أو صفوف tuples. يمكنك تمثيل الكائنات الحقيقية بتنظيم قيم البرنامج في بنى المعطيات، ورأينا مثال ذلك عمليًا على لعبة إكس-أو. مشاريع تدريبية لكي تتدرب، اكتب برامج لتنفيذ المهام الآتية. مدقق لقواميس الشطرنج استعملنا في هذا المقال قيمةً مثل ‎{'1h': 'bking', '6c': 'wqueen', '2g': 'bbishop', '5h': 'bqueen', '3e': 'wking'}‎ لتمثيل رقعة الشطرنج. اكتب دالةً باسم isValidChessBoard()‎ التي تقبل وسيطًا هو قاموس وتعيد القيمة True أو False اعتمادًا إن كان القاموس صالحًا لتمثيل رقعة الشطرنج. تحتوي الرقعة السليمة على ملك أسود واحد وملك أبيض واحد. ويمكن لأيٍ من اللاعبين امتلاك 16 قطعة كحد أقصى، و 8 جنود كحد أقصى، ويجب أن تكون جميع القطع في المجال بين 1a و 8h، أي لا يمكن أن تكون القطعة في المكان 9z. يجب أن تبدأ أسماء القطع بحرف w أو b لتمثيل اللونين الأبيض أو الأسود، متبوعًا بإحدى الكلمات pawn أو knight أو bishop أو rook أو queen أو king. قائمة الأدوات في لعبة نحن نعمل على لعبة فيها قائمة أدوات يمكن أن يمتلكها اللاعب، والتي سننمذجها باستخدام بنية معطيات تتألف من قاموس تكون فيه مفاتيحه هي سلاسل نصية تصف القيمة الموجودة في قائمة الأدوات، وقيمتها هي عدد نصي يمثل عدد الأدوات التي يمتلكها اللاعب مثلًا القاموس ‎{'rope': 1, 'torch': 6, 'gold coin': 42, 'dagger': 1, 'arrow': 12}‎ تعني أن اللاعب يملك حبلًا واحدًا، و 6 شعلات، و 42 قطعة ذهبية …إلخ. اكتب دالةً باسم displayInventory()‎ التي تأخذ أي قاموس يمثل قائمة أدوات ويعرضه بالشكل: Inventory: 12 arrow 42 gold coin 1 rope 6 torch 1 dagger Total number of items: 62 تلميح: يمكنك استخدام حلقة for للمرور على جميع المفاتيح في القاموس: # inventory.py stuff = {'rope': 1, 'torch': 6, 'gold coin': 42, 'dagger': 1, 'arrow': 12} def displayInventory(inventory): print("Inventory:") item_total = 0 for k, v in inventory.items(): # أكمل الشيفرة هنا print("Total number of items: " + str(item_total)) displayInventory(stuff) دالة تحويل قائمة إلى قاموس في لعبة لنفترض أن لاعبنا في اللعبة السابقة قد حصل على غنيمة ممثلة في القائمة الآتية: playerLoot = ['gold coin', 'dagger', 'gold coin', 'gold coin', 'ruby'] اكتب دالةً باسم addToInventory(inventory, addedItems)‎ حيث أن المعامل inventory هو قاموس يمثل قائمة أدوات اللاعب (كما في المثال السابق) والمعامل addedItems يشبه المتغير playerLoot. يجب أن تعيد الدالة addToInventory()‎ قاموسًا يمثل قائمة الأدوات المحدثة. لاحظ أن القائمة الموجودة في addedItems قد تحتوي على عدة نسخ من نفس الأداة. يفترض أن تكون شيفرتك شبيهة بما يلي: def addToInventory(inventory, addedItems): # اكتب الدالة هنا inv = {'gold coin': 42, 'rope': 1} dragonLoot = ['gold coin', 'dagger', 'gold coin', 'gold coin', 'ruby'] inv = addToInventory(inv, dragonLoot) displayInventory(inv) يجب أن يظهر البرنامج السابق (مع الدالة displayInventory()‎ من المثال السابق) الناتج الآتي: Inventory: 45 gold coin 1 rope 1 ruby 1 dagger Total number of items: 48 ترجمة -بتصرف- للفصل Dictionaries And Structuring DATA من كتاب Automate the Boring Stuff with Python. اقرأ أيضًا المقال السابق: القوائم Lists في لغة بايثون أنواع البيانات والعمليات الأساسية في لغة بايثون تعلم لغة بايثون النسخة العربية الكاملة لكتاب البرمجة بلغة بايثون
  4. قبل أن تبدأ بكتابة البرامج عبر بايثون، هنا موضوع مهم يجب أن تعرفه إلى جانب ما تعرفت عليه بالمقالات السابقة من هذه السلسلة، وهو أنواع البيانات خصوصًا القوائم Lists والصفوف tuples. يمكن أن تحتوي القوائم والصفوف على قيم متعددة، مما يجعل كتابة البرامج التي تعالج مقدرًا كبيرًا من البيانات أمرًا سهلًا. ولأن القوائم تستطيع احتواء قوائم أخرى فيها فيمكنك استخدامها لترتيب البيانات ترتيبًا هيكليًا. سنناقش في هذا المقال أساسيات القوائم، وسنتحدث عن التوابع methods، وهي دوال مرتبطة بنوع بيانات معين تجري عمليات عليه. ثم سنتحدث باختصار عن أنواع البيانات المتسلسلة الأخرى مثل الصفوف tuples والسلاسل النصية strings، وكيف تقارن مع بعضها بعضًا. وسنغطي في المقال القادم نوعًا جديدًا من البيانات وهو القاموس dictionary. نوع البيانات list القائمة هي قيمة تحتوي على قيم أخرى متعددة داخلها بترتيب متسلسل. والمصطلح «قيمة القائمة» list value يشير إلى القائمة نفسها، والتي هي القيمة التي يمكن أن تخزن في متغير أو تمرر إلى دالة كغيرها من القيم، ولا تشير إلى القيم الموجودة داخل القائمة. تبدو القائمة بالشكل الآتي: ['cat', 'bat', 'rat', 'elephant'] وكما كنّا نكتب السلاسل النصية محاطةً بعلامتَي اقتباس لتحديد متى تبدأ وتنتهي السلسلة النصية، فتبدأ القائمة بقوس مربع وتنتهي بقوس مربع آخر []. وتسمى القيم داخل القائمة بعناصر القائمة items، ويفصل بين عناصر القائمة بفاصلة. يمكنك إدخال ما يلي في الصدفة التفاعلية للتجربة: >>> [1, 2, 3] [1, 2, 3] >>> ['cat', 'bat', 'rat', 'elephant'] ['cat', 'bat', 'rat', 'elephant'] >>> ['hello', 3.1415, True, None, 42] ['hello', 3.1415, True, None, 42] ➊ >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam ['cat', 'bat', 'rat', 'elephant'] المتغير spam ➊ له قيمة واحدة، وهي قيمة القائمة، ولكن القائمة نفسها تحتوي على عناصر أخرى. لاحظ أن القيمة [] تعني قائمة فارغة لا تحتوي على قيم مثلها كمثل السلسلة النصية الفارغة ''. الوصول إلى عناصر القائمة عبر الفهرس لنقل أن لديك القائمة ['cat', 'bat', 'rat', 'elephant'] مخزنةً في متغير باسم spam، حينها ستكون نتيجة التعبير spam[0]‎ هي القيمة 'cat' ونتيجة التعبير spam[1]‎ هي 'bat' وهلم جرًا للبقية. العدد الصحيح الموجود داخل الأقواس المربعة يسمى فهرسًا index، والقيمة الأولى في القائمة يشار إليها بالفهرس 0، والقيمة الثانية بالفهرس 1، والثالثة بالفهرس 2 …إلخ. يُظهر الشكل التالي قائمةً مسندةً إلى المتغير spam مع توضيح فهارس كل قيمة فيها. لاحظ أن فهرس العنصر الأول هو 0 ويكون فهرس آخر عنصر مساويًا لطول القائمة ناقص واحد. أي أن الفهرس 3 في قائمةٍ لها أربع قيم يشير إلى آخر عنصر. الشكل 1: قائمة مخزنة في متغير مع فهارس كل عنصر فيها على سبيل المثال، أدخل التعابير البرمجية الآتية في الصدفة التفاعلية وابدأ بضبط قيمة المتغير spam: >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[0] 'cat' >>> spam[1] 'bat' >>> spam[2] 'rat' >>> spam[3] 'elephant' >>> ['cat', 'bat', 'rat', 'elephant'][3] 'elephant' ➊ >>> 'Hello, ' + spam[0] ➋ 'Hello, cat' >>> 'The ' + spam[1] + ' ate the ' + spam[0] + '.' 'The bat ate the cat.' لاحظ أن نتيجة التعبير ‎'Hello, ' + spam[0]➊‎ هي 'Hello, ' + 'cat' ذلك لأن نتيجة spam[0]‎ هي 'cat', وبالنهاية ستكون نتيجة التعبير هي السلسلة النصية التالية: 'Hello, cat' ➋. ستحصل على رسالة الخطأ IndexError إذا استخدمت فهرسًا يتجاوز عدد عناصر القائمة. >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[10000] Traceback (most recent call last): File "<pyshell#9>", line 1, in <module> spam[10000] IndexError: list index out of range يجب أن تكون الفهارس أعدادًا صحيحةً فقط، ولا يقبل بالأعداد العشرية؛ فالمثال الآتي يتسبب بخطأ TypeError: >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[1] 'bat' >>> spam[1.0] Traceback (most recent call last): File "<pyshell#13>", line 1, in <module> spam[1.0] TypeError: list indices must be integers or slices, not float >>> spam[int(1.0)] 'bat' يمكن أن تحتوي القائمة على قوائم أخرى عناصر لها، ويمكن الوصول إلى القيم بإدخال أكثر من فهرس: >>> spam = [['cat', 'bat'], [10, 20, 30, 40, 50]] >>> spam[0] ['cat', 'bat'] >>> spam[0][1] 'bat' >>> spam[1][4] 50 يدل الفهرس الأول على أي عنصر من القائمة الأولى يجب استخدامه، والفهرس الثاني يدل على العنصر الموجود في القائمة الثانية، فمثلًا spam[0][1]‎ يطبع 'bat'، وهي القيمة الثانية من القائمة الموجودة في الفهرس الأول. وإذا استخدمت فهرسًا واحدًا فسيطبع البرنامج القائمة الموجودة في ذلك الفهرس كاملةً. الفهارس السالبة صحيحٌ أن أرقام الفهارس تبدًا من 0، لكنك تستطيع استخدام الأعداد الصحيحة السالبة قيمًا للفهارس. فالقيمة ‎-1 تشير إلى آخر عنصر في القائمة، والقيمة ‎-2 تشير إلى العنصر ما قبل الأخير …إلخ. جرب ما يلي في الصدفة التفاعلية: >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[-1] 'elephant' >>> spam[-3] 'bat' >>> 'The ' + spam[-1] + ' is afraid of the ' + spam[-3] + '.' 'The elephant is afraid of the bat.' الحصول على قائمة من قائمة أخرى عبر التقطيع slice ستحصل على قيمة واحدة حين استخدام الفهارس، لكن التقطيع slice يعيد أكثر من قيمة من تلك القائمة على شكل قائمة جديدة؛ ونكتبه ضمن القوسين المربعين كما في الفهارس لكن سنضع عددين صحيحين يفصل بينهما بنقطتين رأسيتين :، لاحظ الاختلاف بينهما: spam[2]‎ ينتج عنصرًا واحدًا موجودًا في الفهرس المحدد. spam[1:4]‎ ينتج قائمة فيها أكثر من عنصر. يكون أول عدد صحيح حين التقطيع هو مكان بدء القطع، والعدد الثاني هو مكان نهاية القطع، وستصل القائمة المقتطعة إلى فهرس العنصر الثاني دون تضمينه في الناتج، وستكون نتيجة القطع هي قائمة جديدة: >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[0:4] ['cat', 'bat', 'rat', 'elephant'] >>> spam[1:3] ['bat', 'rat'] >>> spam[0:-1] ['cat', 'bat', 'rat'] يمكنك اختصارًا أن تزيل أحد الفهرسين من عملية التقطيع مع الإبقاء على النقطتين الرأسيتين :، فإزالة الفهرس الأول تماثل استخدام الفهرس 0 وتشير إلى بداية القائمة، وإزالة الفهرس الثاني تماثل تمرير طول القائمة كاملًا وبالتالي ستنتهي عملية القطع عند نهاية القائمة: >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[:2] ['cat', 'bat'] >>> spam[1:] ['bat', 'rat', 'elephant'] >>> spam[:] ['cat', 'bat', 'rat', 'elephant'] الحصول على طول القائمة عبر الدالة len()‎ ستعيد الدالة len()‎ عدد عناصر قائمة تُمرَّر إليها، وهي تشبه إحصاء عدد المحارف في سلسلة نصية: >>> spam = ['cat', 'dog', 'moose'] >>> len(spam) 3 تغيير القيم في قائمة عبر الفهارس تعودنا أن قيمة المتغير تكون على يسار عامل الإسناد، كما في spam = 42، ويمكننا فعل المثل مع القوائم بكتابة فهرس العنصر الذي نريد تغيير قيمته، مثلًا spam[1] = 'aardvark'‎ يعني «أسند القيمة الموجودة في الفهرس 1 في القائمة spam إلى السلسلة النصية 'aardvark': >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[1] = 'aardvark' >>> spam ['cat', 'aardvark', 'rat', 'elephant'] >>> spam[2] = spam[1] >>> spam ['cat', 'aardvark', 'aardvark', 'elephant'] >>> spam[-1] = 12345 >>> spam ['cat', 'aardvark', 'aardvark', 12345] جمع القوائم Concatenation وتكرارها Replication يمكن أن تجمع القوائم وتكرر كما في السلاسل النصية، فالعامل + يجمع بين قائمتين لإنشاء قائمة جديدة، والعامل * يستخدم لتكرار سلسلة نصية عددًا من المرات: >>> [1, 2, 3] + ['A', 'B', 'C'] [1, 2, 3, 'A', 'B', 'C'] >>> ['X', 'Y', 'Z'] * 3 ['X', 'Y', 'Z', 'X', 'Y', 'Z', 'X', 'Y', 'Z'] >>> spam = [1, 2, 3] >>> spam = spam + ['A', 'B', 'C'] >>> spam [1, 2, 3, 'A', 'B', 'C'] إزالة القيم من القوائم عبر عبارة del تستخدم العبارة del لحذف قيم معينة من قائمة. جميع القيم الموجودة في القائمة بعد العنصر المحذوف سيتغير فهرسها ويصبح أقل بمقدار 1. مثلًا: >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> del spam[2] >>> spam ['cat', 'bat', 'elephant'] >>> del spam[2] >>> spam ['cat', 'bat'] يمكن أن تستعمل العبارة del على المتغيرات العادية أيضًا، وإذا حاولت استخدام أحد المتغيرات بعد حذفه فستحصل على خطأ NameError لأن المتغير لم يعد موجودًا. لكن عمليًا من النادر جدًا أن تحذف قيمة أحد المتغيرات يدويًا وإنما تستعمل استعمالًا رئيسيًا لحذف أحد عناصر القوائم. التعامل مع القوائم حينما تبدأ بالبرمجة، قد يغيرك إنشاء متغيرات لكل قيمة من مجموعة قيم تنتمي إلى مجموعة محددة، فمثلًا لو أردت كتابة أسماء قططي فقد أفكر بكتابة شيفرة كالآتية: catName1 = 'Zophie' catName2 = 'Pooka' catName3 = 'Simon' catName4 = 'Lady Macbeth' catName5 = 'Fat-tail' catName6 = 'Miss Cleo' لكن هذه شيفرة تعيسة! فماذا يحصل لو كان عدد القطط في برنامج متغيرًا؟ فلن يستطيع برنامجك تخزين أسماء قصص لا تملك متغيرات لها. أفضل إلى ذلك أن هذه الأنواع من البرنامج فيها تكرار كثير للشيفرات نفسها، انظر إلى مقدار التكرار في المثال الآتي الذي أنصحك بكتابته باسم AllMyCats1.py وتجربته: print('Enter the name of cat 1:') catName1 = input() print('Enter the name of cat 2:') catName2 = input() print('Enter the name of cat 3:') catName3 = input() print('Enter the name of cat 4:') catName4 = input() print('Enter the name of cat 5:') catName5 = input() print('Enter the name of cat 6:') catName6 = input() print('The cat names are:') print(catName1 + ' ' + catName2 + ' ' + catName3 + ' ' + catName4 + ' ' + catName5 + ' ' + catName6) بدلًا من استخدام أسماء متغيرات متكررة، يمكنك إنشاء متغير واحد يحتوي على قائمة بكل القسم، فهذه نسخة محسنة من المثال السابق، التي نستخدم فيها قائمةً واحدةً يمكن أن تحتوي أي عدد من أسماء القطط التي يمكن أن يدخلها المستخدم. جرب المثال AllMyCats2.py: catNames = [] while True: print('Enter the name of cat ' + str(len(catNames) + 1) + ' (Or enter nothing to stop.):') name = input() if name == '': break catNames = catNames + [name] # جمع القوائم print('The cat names are:') for name in catNames: print(' ' + name) ناتج تجربة المثال السابق: Enter the name of cat 1 (Or enter nothing to stop.): Zophie Enter the name of cat 2 (Or enter nothing to stop.): Pooka Enter the name of cat 3 (Or enter nothing to stop.): Simon Enter the name of cat 4 (Or enter nothing to stop.): Lady Macbeth Enter the name of cat 5 (Or enter nothing to stop.): Fat-tail Enter the name of cat 6 (Or enter nothing to stop.): Miss Cleo Enter the name of cat 7 (Or enter nothing to stop.): The cat names are: Zophie Pooka Simon Lady Macbeth Fat-tail Miss Cleo الفائدة من استخدام القوائم هي أن بياناتك أصبحت مهيكلة هيكلةً أفضل، وسيكون برنامجك مرنًا في معالجة البيانات والتعامل مع مجموعة مشتركة من المتغيرات. استخدام حلقات for مع القوائم تعلمنا في المقال الثاني من هذه السلسلة عن حلقات التكرار for لتنفيذ كتلة من الشيفرات لعدد معين من المرات؛ لكن تقنيًا ما تفعله for هو تكرار الشيفرة داخلها مرةً واحدةً لكل عنصر من عناصر القائمة. فالشيفرة: for i in range(4): print(i) ستخرج الناتج الآتي: 0 1 2 3 هذا لأن القيمة المعادة من تنفيذ range(4)‎ هي قيمة متسلسلة sequence value التي تعاملها بايثون معاملةً شبيهة بالقائمة ‎[0, 1, 2, 3]‎ (سنشرح المتسلسلات Sequences لاحقًا في هذا المقال). سيكون ناتج البرنامج السابق مماثلًا تمامًا لما يلي: for i in [0, 1, 2, 3]: print(i) حلقة التكرار for السابقة تمر على جميع عناصر القائمة ‎[0, 1, 2, 3]‎ وتضبط قيمة المتغير i إلى قيمة كل عنصر منها. من الشائع استخدام التعبير range(len(someList))‎ مع حلقة التكرار for في بايثون للمرور على جميع عناصر قائمة ما: >>> supplies = ['pens', 'staplers', 'flamethrowers', 'binders'] >>> for i in range(len(supplies)): ... print('Index ' + str(i) + ' in supplies is: ' + supplies[i]) Index 0 in supplies is: pens Index 1 in supplies is: staplers Index 2 in supplies is: flamethrowers Index 3 in supplies is: binders استخدام range(len(supplies))‎ في المثال السابق مناسب لأن الشيفرة داخل الحلقة تستطيع الوصول إلى الفهرس عبر المتغير i وإلى القيمة المرتبطة بذاك الفهرس عبر supplies[i]‎، والأفضل من ذلك كله أن range(len(supplies))‎ ستؤدي إلى المرور على جميع عناصر القائمة بغض النظر عن عددها. العاملان in و not in يمكنك معرفة إن كانت قيمةٌ ما موجودةً -أو غير موجودةٍ- في قائمة ما باستخدام العاملين in و not in. وكما في بقية العوامل، يستعمل العاملان in و not in في التعابير وتربطان قيمتين: قيمة نرغب بالبحث عنها في القائمة والقائمة التي نرغب بالبحث فيها؛ وتكون نتيجة هذه التعابير قيمة منطقية بوليانية: >>> 'howdy' in ['hello', 'hi', 'howdy', 'heyas'] True >>> spam = ['hello', 'hi', 'howdy', 'heyas'] >>> 'cat' in spam False >>> 'howdy' not in spam False >>> 'cat' not in spam True فمثلًا يسمح البرنامج الآتي للمستخدم بكتابة اسم قطته ليتأكد إن كان اسمها ضمن قائمة من أسماء القطط. احفظه باسم myPets.py وجربه: myPets = ['Zophie', 'Pooka', 'Fat-tail'] print('Enter a pet name:') name = input() if name not in myPets: print('I do not have a pet named ' + name) else: print(name + ' is my pet.') سيشبه الناتج ما يلي: Enter a pet name: Footfoot I do not have a pet named Footfoot خدعة للإسناد المتعدد هنالك اختصار يسمى تقنيًا بنشر الصفوف tuple unpacking يسمح لنا بإسناد عدة قيمة لمتغيرات اعتمادًا على قائمة في سطر واحد. فبدلًا من كتابة: >>> cat = ['fat', 'gray', 'loud'] >>> size = cat[0] >>> color = cat[1] >>> disposition = cat[2 نستطيع أن نكتب: >>> cat = ['fat', 'gray', 'loud'] >>> size, color, disposition = cat يجب أن يكون عدد المتغيرات وطول القائمة متساوٍ تمامًا، وإلا فستعطيك بايثون الخطأ ValueError: >>> cat = ['fat', 'gray', 'loud'] >>> size, color, disposition, name = cat Traceback (most recent call last): File "<pyshell#84>", line 1, in <module> size, color, disposition, name = cat ValueError: not enough values to unpack (expected 4, got 3) استخدام الدالة enumerate()‎ مع القوائم بدلًا من استخدام range(len(someList))‎ مع حلقة تكرار for للوصول إلى قيمة الفهرس لكل عنصر من عناصر القائمة، فيمكننا استدعاء الدالة enumerate()‎ بدلًا منها. ففي كل دورة لحلقة التكرار ستعيد الدالة enumerate()‎ قيمتين: فهرس العنصر الموجود في القائمة والعنصر نفسه على شكل قائمة. فالشيفرة الآتية مماثلة في الوظيفة للمثال الموجود في قسم «استخدام حلقات for مع القوائم»: >>> supplies = ['pens', 'staplers', 'flamethrowers', 'binders'] >>> for index, item in enumerate(supplies): ... print('Index ' + str(index) + ' in supplies is: ' + item) Index 0 in supplies is: pens Index 1 in supplies is: staplers Index 2 in supplies is: flamethrowers Index 3 in supplies is: binders الدالة enumerate()‎ مفيدة إن كنت تريد الوصول إلى العنصر وفهرسه ضمن حلقة التكرار. استخدام الدالتين random.choice()‎ و random.shuffle()‎ مع القوائم الوحدة random فيها عدة دوال تقبل القوائم كمعاملات لها. الدالة random.choice()‎ تعيد عنصرًا مختارًا عشوائيًا من القائمة: >>> import random >>> pets = ['Dog', 'Cat', 'Moose'] >>> random.choice(pets) 'Dog' >>> random.choice(pets) 'Cat' >>> random.choice(pets) 'Cat' يمكنك أن تقول أن random.choice(someList)‎ هي نسخة مختصرة من someList[random.randint(0, len(someList) – 1]‎. الدالة random.shuffle()‎ تعيد ترتيب العناصر ضمن القائمة عشوائيًا، وتعدل القائمة مباشرة دون إعادة قائمة جديدة: >>> import random >>> people = ['Alice', 'Bob', 'Carol', 'David'] >>> random.shuffle(people) >>> people ['Carol', 'David', 'Alice', 'Bob'] >>> random.shuffle(people) >>> people ['Alice', 'David', 'Bob', 'Carol'] عوامل الإسناد المحسنة من الشائع حين إسناد قيمة ما إلى متغير أن تستعمل قيمة المتغير الابتدائية أساسًا للقيمة الجديدة. فمثلًا بعد إسنادك القيمة 42 للمتغير spam وأردت زيادة قيمة المتغير بمقدار واحد: >>> spam = 42 >>> spam = spam + 1 >>> spam 43 يمكنك بدلًا من ذلك استخدام عامل الإسناد المحسن ‎+=‎ لنفس النتيجة: >>> spam = 42 >>> spam += 1 >>> spam 43 هنالك عوامل إسناد محسنة للعوامل + و - و * و/ و % موضحة في الجدول التالي: عامل الإسناد المحسن التعبير البرمجي المكافئ spam += 1 spam = spam + 1 spam -= 1 spam = spam - 1 spam *= 1 spam = spam * 1 spam /= 1 spam = spam / 1 spam %= 1 spam = spam % 1 الجدول 1: عوامل الأسناد المحسنة يمكن استخدام عامل الإسناد المحسن ‎+=‎ لدمج قائمتين، والمعامل ‎*=‎ لتكرار قائمة: >>> spam = 'Hello,' >>> spam += ' world!' >>> spam 'Hello world!' >>> olive = ['Zophie'] >>> olive *= 3 >>> olive ['Zophie', 'Zophie', 'Zophie'] التوابع Methods يمكننا القول مجازًا أن التابع method يكافئ الدوال لكنها «تستدعى على» قيمة ما. فمثلًا إذا استدعيت دالة القوائم index()‎ -التي سنشرحها بعد قليل- على قائمة فستكتب: list.index('hello')‎؛ أي أن التابع يأتي بعد القيمة ويفصل عنها بنقطة. لكل نوع من أنواع البيانات مجموعة توابع خاصة به، ولنوع بيانات القوائم عدد من التوابع المفيدة للبحث والإضافة والحذف ومختلف عمليات التعديل الأخرى. العثور على قيمة في قائمة عبر التابع index()‎ تمتلك القوائم التابع index()‎ الذي تقبل معاملًا وهو القيمة التي سيجري البحث عنها في القائمة، وإذا كان العنصر موجودًا فسيعاد فهرس ذاك العنصر، وإذا لم يكن موجودًا فستطلق بايثون الخطأ ValueError: >>> spam = ['hello', 'hi', 'howdy', 'heyas'] >>> spam.index('hello') 0 >>> spam.index('heyas') 3 >>> spam.index('howdy howdy howdy') Traceback (most recent call last): File "<pyshell#31>", line 1, in <module> spam.index('howdy howdy howdy') ValueError: 'howdy howdy howdy' is not in list وعند وجود قيم مكررة في القائمة فسيعاد الفهرس لأول قيمة يُعثَر عليها، لاحظ أن التابع index()‎ قد أعاد 1 وليس 3 في هذا المثال: >>> spam = ['Zophie', 'Pooka', 'Fat-tail', 'Pooka'] >>> spam.index('Pooka') 1 إضافة قيم إلى القوائم عبر append()‎ و insert()‎ استخدم التابعين append()‎ و insert()‎ لإضافة قيم جديدة إلى قائمة: >>> spam = ['cat', 'dog', 'bat'] >>> spam.append('moose') >>> spam ['cat', 'dog', 'bat', 'moose'] أدى استدعاء التابع append()‎ إلى إضافة قيمة المعامل الممرر إليه إلى نهاية القائمة. التابع insert()‎ يضيف قيمةً جديدةً إلى أي فهرس في القائمة، ويكون الوسيط الأول الممرر إلى التابع insert()‎ هو فهرس القيمة الجديدة، والوسيط الثاني هو القيمة التي نريد إضافتها: >>> spam = ['cat', 'dog', 'bat'] >>> spam.insert(1, 'chicken') >>> spam ['cat', 'chicken', 'dog', 'bat'] لاحظ أن الشيفرة التي كتبناها هي spam.append('moose')‎ و spam.insert(1, 'chicken')‎ وليست spam = spam.append('moose')‎ أو spam = spam.insert(1, 'chicken')‎، إذ لا يعيد التابع append()‎ أو insert()‎ القيمة الجديدة للقائمة spam (وفي الواقع تكون نتيجة استدعائها هي None، فلا حاجة إلى تخزين قيمة استدعاء تلك التوابع في متغير، بل تعدل تلك التوابع القائمةَ مباشرةً. سنتحدث بالتفصيل عن القوائم القابلة للتغيير وغير القابلة للتغيير لاحقًا في هذا المقال. التوابع التي ترتبط بنوع بيانات محدد -مثل append()‎ و insert()‎- هي خاصة بذاك النوع، فلا يمكن استخدامها على قيم أخرى مثل السلاسل النصية أو الأعداد الصحيحة. لاحظ ظهور رسالة الخطأ AttributeError في المثال الآتي: >>> eggs = 'hello' >>> eggs.append('world') Traceback (most recent call last): File "<pyshell#19>", line 1, in <module> eggs.append('world') AttributeError: 'str' object has no attribute 'append' >>> olive = 42 >>> olive.insert(1, 'world') Traceback (most recent call last): File "<pyshell#22>", line 1, in <module> olive.insert(1, 'world') AttributeError: 'int' object has no attribute 'insert' إزالة القيم من القوائم عبر التابع remove()‎ نمرر إلى التابع remove()‎ القيمة التي نريد حذفها من القائمة التي يستدعى التابع عليها: >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam.remove('bat') >>> spam ['cat', 'rat', 'elephant'] إذا حاولنا حذف قيمة غير موجودة في القائمة فسيظهر الخطأ ValueError: >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam.remove('chicken') Traceback (most recent call last): File "<pyshell#11>", line 1, in <module> spam.remove('chicken') ValueError: list.remove(x): x not in list إذا تكررت القيمة التي نريد حذفها أكثر من مرة في القائمة فستزال أول نسخة من تلك القيمة: >>> spam = ['cat', 'bat', 'rat', 'cat', 'hat', 'cat'] >>> spam.remove('cat') >>> spam ['bat', 'rat', 'cat', 'hat', 'cat'] لاحظ أن العبارة del مفيدة حينما تعرف فهرس العنصر الذي تريد حذفه من القائمة، بينما يفيد التابع remove()‎ إذا كنت تعرف قيمة العنصر الذي تريد حذفه. ترتيب عناصر قائمة عبر التابع sort()‎ يمكن ترتيب القوائم التي تحتوي على أعداد أو على سلاسل نصية باستخدام التابع sort()‎: >>> spam = [2, 5, 3.14, 1, -7] >>> spam.sort() >>> spam [-7, 1, 2, 3.14, 5] >>> spam = ['ants', 'cats', 'dogs', 'badgers', 'elephants'] >>> spam.sort() >>> spam ['ants', 'badgers', 'cats', 'dogs', 'elephants'] يمكنك تمرير القيمة True قيمةً للوسيط المسمى reverse لجعل التابعsort()‎ يرتب النتائج ترتيبًا عكسيًا: >>> spam.sort(reverse=True) >>> spam ['elephants', 'dogs', 'cats', 'badgers', 'ants'] هنالك ثلاثة أمور أساسية يجب عليك معرفتها حول التابع sort()‎: بدايةً يرتب التابع sort()‎ عناصر القائمة مباشرةً دون إعادة قائمة جديدة، أي ليس هنالك فائدة من كتابة شيء يشبه spam = spam.sort()‎. الأمر الثاني هو أنك لا تستطيع ترتيب القوائم التي تحتوي على قيم عددية ونصية في آن واحد، لأن بايثون لا تعرف كيف تقارن هذه القيم مع بعضها بعضًا. لاحظ الخطأ TypeError في المثال الآتي: >>> spam = [1, 3, 2, 4, 'Alice', 'Bob'] >>> spam.sort() Traceback (most recent call last): File "<pyshell#70>", line 1, in <module> spam.sort() TypeError: '<' not supported between instances of 'str' and 'int' وأخيرًا، يستعمل التابع sort()‎ ترتيب ASCII للسلاسل النصية بدلًا من الترتيب الهجائي، هذا يعني أن الأحرف الكبيرة في الإنكليزية تأتي قبل الأحرف الصغيرة أي أن a سيكون بعد Z مباشرةً: >>> spam = ['Alice', 'ants', 'Bob', 'badgers', 'Carol', 'cats'] >>> spam.sort() >>> spam ['Alice', 'Bob', 'Carol', 'ants', 'badgers', 'cats'] إذا أردت ترتيب القيم ترتيبًا هجائيًا، فمرر القيمة str.lower للوسيط المسمى key في التابع sort()‎: >>> spam = ['a', 'z', 'A', 'Z'] >>> spam.sort(key=str.lower) >>> spam ['a', 'A', 'z', 'Z'] هذا سيجعل التابع sort()‎ يتعامل مع جميع عناصر القائمة كما لو أنها في حالة الأحرف الصغيرة دون تعديل القيم نفسها. قلب ترتيب عناصر قائمة عبر التابع reverse()‎ إذا احتجت إلى قلب ترتيب عناصر إحدى القوائم سريعًا فاستعمل التابع reverse()‎: >>> spam = ['cat', 'dog', 'moose'] >>> spam.reverse() >>> spam ['moose', 'dog', 'cat'] وكما في التابع sort()‎، لا يعيد التابع reverse()‎ قائمةً بل يعدلها مباشرةً، ولهذا نكتب spam.reverse()‎ وليس spam = spam.reverse()‎. استثناءات من قواعد المسافات البادئة في بايثون في أغلبية الحالات، يكون عدد المسافات البادئة قبل كل سطر برمجي دليلًا يقول لمفسر لغة بايثون في أي كتلة ينتمي ذاك السطر. لكن هنالك بعض الاستثناءات لهذه القائمة، فمثلًا يمكن أن تمتد كتابة القائمة على أكثر من سطر في الشيفرة المصدرية، ولا تهم المسافة البادئة هنا، لأن مفسر بايثون يفهم أن تعريف القائمة لا ينتهي إلا بوجود قوس الإغلاق [. فيمكن أن يكون لدينا شيفرة كما يلي: spam = ['apples', 'oranges', 'bananas', 'cats'] print(spam) لكن عمليًا يستعمل أغلبية المبرمجين المسافات البادئة استعمالًا صحيحًا لتسهل عملية قراءة شيفرتهم. يمكنك أن تقسم تعليمة برمجية واحدة على أكثر من سطر باستخدام محرف إكمال السطر \ في نهاية السطر، يمكنك أن تقول أن محرف إكمال السطر \ يقول «سنكمل هذه التعليمة في السطر القادم»، ولن تكون المسافة البادئة في السطر الذي يلي محرف إكمال السطر \ مهمةً، فالشيفرة الآتية صحيحة: print('Four score and seven ' + \ 'years ago...') ستستفيد من هذه الأمور حينما تحتاج إلى تقسيم الأسطر الطويلة إلى أسطر أقصر لتسهيل قابلية قراءتها. مثال عملي: إعادة كتابة برنامج الكرة السحرية باستخدام القوائم يمكننا كتابة نسخة أفضل من مثال الكرة السحرية الذي كتبناه عبر عبارات elif سابقًا، إذ يمكننا إنشاء قائمة واحدة وسنجعل البرنامج يتعامل معها مباشرةً. احفظ ما يلي في ملف باسم magic8ball2.py: import random messages = ['It is certain', 'It is decidedly so', 'Yes definitely', 'Reply hazy try again', 'Ask again later', 'Concentrate and ask again', 'My reply is no', 'Outlook not so good', 'Very doubtful'] print(messages[random.randint(0, len(messages) - 1)]) حينما تجرب هذا البرنامج فسيبدو ناتجه كما في المثال magic8ball.py الأصلي. لاحظ التعبير الذي استخدمناه كفهرس للقائمة messages:‏ random.randint (0, len(messages) - 1)‎. وهو يولد عدد عشوائيًا نستعمله كفهرس بغض النظر عن عدد العناصر الموجودة في القائمة messages. ذاك التعبير يولد قيمةً عشوائيًا بين 0 وقيمة len(messages) - 1، والقائدة من هذه الطريقة أننا نستطيع إضافة وإزالة العناصر من القائمة messages دون الحاجة إلى تغيير أي شيفرات أخرى، فعندما تحدث شيفرة البرنامج مستقبلًا لإضافة عناصر جديدة إلى القائمة، فلا تضطر إلى تعديلات إضافية، وكلما قللت من الأمور التي تغيرها قلَّت احتمالية استحداث علل برمجية جديدة. أنواع البيانات المتسلسلة Sequence Data Types القوائم هي إحدى أنواع البيانات التي تمثل قائمةً من القيم، لكنها ليست الوحيدة. فمثلًا السلاسل النصية strings والقوائم lists متشابهة جدًا إذا تخيلنا أن السلسلة النصية هي «قائمة» من المحارف. أنواع البيانات المتسلسلة في بايثون تتضمن القوائم lists، والسلاسل النصية strings، وكائنات المجالات range objects المعادة من الدالة range()‎، والصفوف tuples. أغلبية الأمور التي تستطيع فعلها مع القوائم يمكنك فعلها مع بقية أنواع البيانات المتسلسلة، بما في ذلك: الفهرسة، والتقطيع، واستخدامها مع حلقات for، واستخدام len()‎، واستخدام العوامل in و not in. جرب المثال الآتي لترى ذلك عمليًا: >>> name = 'Zophie' >>> name[0] 'Z' >>> name[-2] 'i' >>> name[0:4] 'Zoph' >>> 'Zo' in name True >>> 'z' in name False >>> 'p' not in name False >>> for i in name: ... print('* * * ' + i + ' * * *') * * * Z * * * * * * o * * * * * * p * * * * * * h * * * * * * i * * * * * * e * * * أنواع البيانات القابلة وغير القابلة للتعديل تختلف القوائم والسلاسل النصية عن بعضها اختلافًا جوهريًا، فالقوائم هي من أنواع البيانات القابلة للتعديل mutable data type، فيمكن أن تضاف أو تحذف أو تعدل عناصرها؛ بينما السلاسل النصية غير قابلة للتعديل immutable، فإذا حاولت ضبط قيمة أحد محارف السلسلة النصية يدويًا فسيحدث الخطأ TypeError كما في المثال الآتي: >>> name = 'Zophie a cat' >>> name[7] = 'the' Traceback (most recent call last): File "<pyshell#50>", line 1, in <module> name[7] = 'the' TypeError: 'str' object does not support item assignment الطريقة المعتمدة «لتعديل» سلسلة نصية هي استخدام التقطيع والجمع لبناء سلسلة نصية جديدة اعتمادًا على أجزاء من السلسلة النصية القديمة: >>> name = 'Zophie a cat' >>> newName = name[0:7] + 'the' + name[8:12] >>> name 'Zophie a cat' >>> newName 'Zophie the cat' استعملنا [0:7] و [8:12] للإشارة إلى المحارف التي لا نريد تعديلها، لاحظ أن السلسلة النصية الأصلية 'Zophie a cat' لم تعدل، بل أنشأنا سلسلة نصية جديدة. وصحيحٌ أن القوائم قابلة للتعديل، لكن السطر الثاني في المثال الآتي لن يعدل القائمة eggs: >>> eggs = [1, 2, 3] >>> eggs = [4, 5, 6] >>> eggs [4, 5, 6] لم تبدل القيم في القائمة eggs هنا؛ بل أُنشِئت قائمة جديدة ‎[4, 5, 6]‎ وكتبت فوق القائمة القديمة ‎[1, 2, 3]‎ كما في الشكل الآتي: الشكل 2: ما يحدث عند إسناد قائمة جديدة إلى متغير إذا أردت فعليًا تعديل القائمة الأصلية المخزنة في eggs لتحتوي على ‎[4, 5, 6]‎، فعليك فعل شيء يشبه ما يلي: ‎>>> eggs = [1, 2, 3] >>> del eggs[2] >>> del eggs[1] >>> del eggs[0] >>> eggs.append(4) >>> eggs.append(5) >>> eggs.append(6) >>> eggs [4, 5, 6] يوضح الشكل الموالي التعديلات السبع التي جرت على القائمة eggs لتصل إلى النتيجة النهائية. الشكل 3: تُغيِّر العبارة del والتابع append()‎ قيمة القائمة مباشرة تغيير قيمة نوع بيانات قابل للتعديل (مثل استخدام العبارة del والتابع append()‎ كما في الثالث السابق) سيؤدي إلى تغيير القيمة في مكانها، وذلك لأن قيمة المتغير لا تبدل إلى قائمة جديدة. قد يبدو لك الآن أن الفرق بين أنواع البيانات القابلة وغير القابلة للتعديل تافه ولا يهم، لكن قسم «تمرير المرجعيات» سيشرح لك الفرق في السلوك حين استدعاء الدوال مع وسائط قابلة للتعديل ووسائط غير قابلة للتعديل. لكن قبل ذلك دعنا نتعلم عن نوع بيانات جديد وهو الصفوف tuples، وهو يشبه القوائم لكنه غير قابل للتعديل. الصفوف Tuples يكاد يماثل نوع البيانات tuple القوائم تمامًا، مع استثناء أمرين اثنين: الأول أننا نعرف الصفوف عبر قوسين هلاليين () بدلًا من القوسين المربعين []: >>> eggs = ('hello', 42, 0.5) >>> eggs[0] 'hello' >>> eggs[1:3] (42, 0.5) >>> len(eggs) 3 والثاني -وهو المهم- أن الصفوف هي نوع بيانات غير قابل للتعديل كما في السلاسل النصية، أي لا يمكننا تعديل قيم عناصر الصف أو إضافتها أو حذفها. لاحظ رسالة الخطأ TypeError حين تنفيذ المثال الآتي: >>> eggs = ('hello', 42, 0.5) >>> eggs[1] = 99 Traceback (most recent call last): File "<pyshell#5>", line 1, in <module> eggs[1] = 99 TypeError: 'tuple' object does not support item assignment إذا كانت لديك قيمة واحدة في الصف، فيمكنك أن تطلب من بايثون تعريف ذاك الصف بوضع فاصلة بعد تلك القيمة، وإلا فستظن بايثون أنك كتبت قيمةً عاديةً ضمن قوسين، فالفاصلة هنا هي ما سيخبر بايثون أن ما تريده هو نوع البيانات tuple. لاحظ أن من الطبيعي في بايثون وجود فاصلة بعد آخر عنصر في صف أو قائمة على عكس بعض لغات البرمجة الأخرى. جرب الدالة type()‎ في المثال الآتي لترى الفرق الذي يحدثه استخدام الفاصلة بعد القيمة عمليًا: >>> type(('hello',)) <class 'tuple'> >>> type(('hello')) <class 'str'> يمكنك استخدام الصفوف في برنامجك لتقول لمن يقرأه من المبرمجين أنك لا تنوي لهذه السلسلة من القيم أن تتغير؛ أي لو أردت قيمًا متسلسلة مرتبة لا تتغير فاستخدم الصفوف. ميزة أخرى من مزايا الصفوف بدلًا من القوائم هي أن بايثون تستطيع تطبيق بعض التحسينات الداخلية لمعالجة الصفوف معالجةً أسرع من القوائم، وذلك لعلمها أنها قيم متسلسلة غير قابلة للتعديل. تبديل أنواع التسلسلات باستخدام الدوال list()‎ و tuple()‎ كما تعيد الدالة str(42)‎ القيمة '42' وهو التمثيل النصي للرقم 42؛ تعيد الدالتان list()‎ و tuple()‎ نسخة القائمة والصف من القيم الممررة إليها. جرب المثال الآتي ولاحظ كيف أن نوع القيمة المعادة مختلف عن نوع القيمة الممررة: >>> tuple(['cat', 'dog', 5]) ('cat', 'dog', 5) >>> list(('cat', 'dog', 5)) ['cat', 'dog', 5] >>> list('hello') ['h', 'e', 'l', 'l', 'o'] قد يفيدك تحويل صف إلى قائمة إن احتجت إلى نسخة قابلة للتعديل من قيمة ذاك الصف. المرجعيات References كما رأيت سابقًا، «تُخزِّن» المتغيرات قيم السلاسل النصية والأعداد، لكن هذا تبسيط لما تقوم به بايثون فعلًا. فتقنيًا تخزن المتغيرات مرجعيةً أو إشارةً إلى مكان تخزين قيمة المتغير في ذاكرة الحاسوب: >>> spam = 42 >>> cheese = spam >>> spam = 100 >>> spam 100 >>> cheese 42 فعندما تسند القيمة 42 إلى المتغير spam فأنت تنشِئ القيمة 42 في ذاكرة الحاسوب ثم تخزِّن مرجعيةً reference إليها في المتغير spam، وعندما تنسخ القيمة في spam وتسندها إلى المتغير cheese فأنت فعليًا تنسخ المرجعية إلى القيمة 42 في ذاكرة الحاسوب وليس القيمة نفسها، أي أن كلا المتغيرين spam و cheese يشيران إلى القيمة 42 نفسها في ذاكرة الحاسوب. ثم حينما تغيّر قيمة المتغير spam إلى 100 فأنت تنشِئ قيمةً جديدةً وهي 100 ثم تخزن مرجعيةً إليها في المتغير spam، وهذا لا يؤثر على القيمة الموجودة في المتغير cheese. تذكر أن القيم العددية من أنواع البيانات غير القابلة للتعديل، أي أن تغيير قيمة spam سيؤدي إلى تغيير المرجعية التي تشير إليها في الذاكرة. لكن لا تعمل القوائم بهذه الطريقة، وذلك لأن القوائم من أنواع البيانات القابلة للتعديل. هذا المثال يسهِّل فهم الآلية السابقة والفروق بين القوائم وغيرها من أنواع البيانات: ➊ >>> spam = [0, 1, 2, 3, 4, 5] ➋ >>> cheese = spam # ستنسخ المرجعية وليست القائمة ➌ >>> cheese[1] = 'Hello!' # وهذا ما يغير قيمة عنصر القائمة >>> spam [0, 'Hello!', 2, 3, 4, 5] >>> cheese # يشير المتغير إلى القائمة نفسها [0, 'Hello!', 2, 3, 4, 5] قد يبدو الناتج السابق غريبًا بالنسبة إليك، فأنت تعدل فيه على القائمة cheese لكن التغييرات حدثت على المتغير cheese و spam معًا! عند إنشائك للقائمة ➊ فأنت تخزن مرجعيةً إليها في المتغير spam، وفي السطر التالي ➋ نسخت المرجعية الموجودة في spam إلى cheese وليس القائمة نفسها. وهذا يعني أن القيم المخزنة في المتغيرين spam و cheese تشير إلى القائمة نفسها. لاحظ أن هنالك قائمة واحدة لأن القائمة لم تنسَخ بحد ذاتها بل نُسِخَت المرجعية إليها؛ لذا حينما تعدل أحد عناصر القائمة cheese ➌ فأنت تعدل نفس القائمة التي يُشار إليها عبر المتغير spam. تذكر أن المتغيرات تشبه الصناديق التي تحتوي على قيم، والرسومات التوضيحية التي رأيتها في هذا الفصل لحد الآن ليست دقيقة تمامًا لأن من غير الممكن احتواء قائمة داخل صندوق، بل تحتوي الصناديق على إشارات لتلك القوائم، وتلك الإشارات تملك معرفات ID تستعملها بايثون داخليًا، ولتصحيح تخيلنا للمتغيرات والقوائم فانظر إلى الشكل الآتي: الشكل 4: تخزن مرجعية إلى قائمة في المتغيرات، وليست القائمة نفسها في الشكل الآتي 5 سننسخ المرجعية الموجودة في spam إلى cheese، لاحظ تخزين قيمة المرجعية في cheese وليس القائمة. لاحظ كيف يشير كلا المتغيرين إلى القائمة نفسها: الشكل 5: إسناد قيمة متغير إلى آخر ينسخ المرجعية وليس القائمة وحينما تعدل القائمة التي يشير إليها المتغير cheese فأنت تعدل القائمة التي يشير إليها spam أيضًا، لأنهما يشيران إلى القائمة نفسها، يمكنك ملاحظة ذلك في الشكل الموالي: الشكل 6: تغيير عنصر في قائمة يشار إليها من متغيرين مختلفين صحيحٌ أن بايثون تخزن مرجعيات في المتغيرات، لكن من الشائع أن يقول المطورون أن «المتغيرات تحتوي على قيم» وليس «المتغيرات تحتوي على مرجعيات تشير إلى قيم». المعرفات والدالة id()‎ قد تتساءل لماذا يطبق السلوك السابق الغريب الذي ناقشناه في القسم السابق على القوائم القابلة للتعديل ولا يحدث على القيم غير القابلة للتعديل كالأعداد أو السلاسل النصية. يمكننا استخدام الدالة ()id لفهم ذلك، فكل القيم في بايثون لها معرف خاص بها يمكن الحصول عليه باستخدام الدالة id()‎: >>> id('Howdy') # ستختلف القيمة المعادة في حاسوبك 44491136 عندما تشغل بايثون العبارة البرمجية id('Howdy')‎ فهي تنشِئ السلسلة النصية 'Howdy' في ذاكرة حاسوبك، ويعاد عنوان الذاكرة الرقمي الذي خُزِّنَت السلسلة النصية فيه عبر الدالة id()‎، وتختار بايثون العنوان اعتمادًا على أي بايتات تتوافر في ذاكرة حاسوبك في وقت التنفيذ، لذا ستختلف القيمة في كل مرة تشغل فيها الشيفرة. وككل السلاسل النصية، السلسلة 'Howdy' غير قابلة للتعديل، وإذا حاولت «تعديل» قيمة السلسلة النصية الموجودة في متغير، فستُنشَأ سلسلة نصية جديدة في مكان آخر في الذاكرة ثم سيشير المتغير إلى السلسلة النصية الجديدة. جرب المثال الآتي ولاحظ تغيير المعرف الذي يشير إليه المتغير olive: >>> olive = 'Hello' >>> id(olive) 44491136 >>> olive += ' world!' # سلسلة نصية جديدة >>> id(olive) # يشير المتغير إلى سلسلة نصية مختلفة 44609712 لكن يمكن تعديل القوائم لأنها من أنواع البيانات القابلة للتعديل. فالتابع append()‎ لا ينشِئ قائمة جديدة حين تنفيذه، بل يعدل القائمة الموجودة، ونسمي هذا السلوك «بالتعديل في المكان» in-place: >>> eggs = ['cat', 'dog'] # إنشاء قائمة جديدة >>> id(eggs) 35152584 >>> eggs.append('moose') # يضيف التابع القيم مباشرة >>> id(eggs) # يشير المتغير إلى نفس القائمة السابقة 35152584 >>> eggs = ['bat', 'rat', 'cow'] # إنشاء قائمة جديدة لها معرف مختلف >>> id(eggs) # يشير المتغير إلى قائمة مختلفة كليًا 44409800 إذا أشار متغيران أو أكثر إلى القائمة نفسها (كما في المثال في القسم السابق) ثم تغيرت قيمة القائمة، فستحدث التغييرات على كلا المتغيرات لأنهما يشيران إلى القائمة نفسها. التوابع append()‎ و extend()‎ و remove()‎ و sort()‎ و reverse()‎ وغيرها من توابع القوائم ستعمل القوائم في مكانها. جامع القمامة التلقائي في Python (أي Garbage Collector) يحذف أي قيم لا يشار إليها من المتغيرات لكي يُفرِّغ الذاكرة، وهذا رائع لأن الإدارة اليدوية للذاكرة في لغات البرمجة الأخرى هي سبب رئيسي للعلل البرمجية. تمرير المرجعيات من المهم فهم المرجعيات لاستيعاب كيف تمرر الوسائط إلى الدوال. حين استدعاء دالة ما، فإن القيم الممررة كوسائط arguments تنسخ إلى المعاملات parameters، وبالنسبة إلى القوائم (والقواميس التي سنتعرف عليها في المقال القادم) هذا يعني أن المرجعية التي تشير إلى القائمة ستنسخ من الوسيط إلى المعامل، ولكي تعي آثار ذلك جرب المثال الآتي باسم passingReference.py: def eggs(someParameter): someParameter.append('Hello') spam = [1, 2, 3] eggs(spam) print(spam) لاحظ أنه حين استدعاء eggs()‎ فلن تستعمل القيمة المعادة من الدالة لإسناد قيمة جديدة إلى المتغير spam، بل ستعدل المتغير spam في مكانه مباشرةً؛ وسيخرج الناتج الآتي: [1, 2, 3, 'Hello'] وصحيحٌ أن قيمة spam نسخت إلى someParameter لكن ما نسخ فعليًا هو المرجعية إلى نفس القائمة، ولهذا سيؤدي استدعاء التابع append('Hello')‎ إلى تعديل القائمة خارج الدالة. أبقِ هذا السلوك في ذهنك أثناء كتابة الشيفرات، فلو نسيت كيف تتعامل بايثون مع القوائم والقواميس فستقع في أخطاء وعلل كان من السهل تفاديها. الدالة copy()‎ و deepcopy()‎ في الوحدة copy صحيحٌ أن تمرير المرجعيات للإشارة إلى القوائم والقواميس يسهل التعامل معها، لكن إن كانت لدينا دالة تغير القائمة أو القاموس الممرر إليها وكنّا لا نريد إجراء تلك التعديلات على القائمة أو القاموس الأصليين، فحينها يمكننا الاستفادة من الوحدة التي توفرها بايثون باسم copy التي توفر الدالتين copy()‎ و deepcopy()‎. أول دالة منهما copy.copy()‎ تنشِئ نسخةً طبق الأصل من قيمة قابلة للتعديل كقوائم أو القواميس: >>> import copy >>> spam = ['A', 'B', 'C', 'D'] >>> id(spam) 44684232 >>> cheese = copy.copy(spam) >>> id(cheese) # قائمة مختلفة بمعرف مختلف 44685832 >>> cheese[1] = 42 >>> spam ['A', 'B', 'C', 'D'] >>> cheese ['A', 42, 'C', 'D'] يشير المتغيران spam و cheese إلى قوائم مختلفة، ولهذا السبب سنجد أن القائمة المشار إليها عبر المتغير cheese هي من تغيرت حينما ضبطنا العنصر ذا الفهرس 1 إلى القيمة 42. لاحظ في الشكل التالي أن أرقام المعرفات ID مختلفة لكلا المتغيرين، لأن كل واحد منهما يشير إلى قائمة مختلفة. الشكل 7: نسخ القائمة عبر copy()‎ ينشِئ قائمة جديدة يمكن تعديلها بشكل مستقل عن القائمة الأصلية إذا كانت لديك قائمة ترغب بنسخ محتوياتها أيضًا فاستعمال الدالة copy.deepcopy()‎ بدلًا من copy.copy()‎. ستنسخ الدالة deepcopy()‎ القوائم الداخلية أيضًا. برنامج قصير: لعبة الحياة لعبة الحياة لكونواي Conway’s Game of Life هي مثال عن خلايا ذاتية السلوك cellular automata: مجموعة من القوائم التي تحكم سلوك حقل مؤلف من خلايا منفصلة. عمليًا هذه طريقة لإنشاء أشكال متحركة جميلة، يمكنك أن ترسل كل خطوة على ورقة رسم بياني، وتمثل المربعات في ورقة الرسم الخلايا. المربع الممتلئ هو خلية «حية»، بينما المربع الفارغ هو خلية «ميتة». تموت أي خلية حية لها أقل من اثنتين من الجيران الأحياء. أي خلية حية لها اثنتين أو ثلاثة جيران من الخلايا الحية تعيش إلى الجيل القادم. تموت أي خلية حية لها أكثر من ثلاثة جيران من الخلايا الحية. أي خلية ميتة تصبح حية عندما يصبح حولها بالضبط ثلاثة من الخلايا الأحياء. تموت أي خلية أخرى أو تبقى ميتة في الجيل القادم. يمكنك النظر إلى تمثيل لتقدم أجيل لعبة الحياة في الشكل الآتي: الشكل 8: أربع خطوات أو أجيال في لعبة الحياة صحيح أن القواعد بسيطة نسبيًا، لكن قد تحدث بعض السلوكيات المثيرة، فيمكن أن تتحرك الأنماط في لعبة الحياة أو أن تتكاثر، أو حتى تحاكي عمل المعالجات المركزية CPUs. لكن في أساس كل هذه الأنماط المعقدة برنامج بسيط. يمكننا استخدام قائمة تحتوي على قوائم داخلها لتمثيل الحقل ثنائي الأبعاد، وتمثل القوائم الداخلية عمودًا من المربعات، وتخزن القيمة '#' للخلايا الحية، والقيمة ' ' للخلايا الميتة. اكتب المثال الآتي في ملف باسم conway.py، ولا مشكلة إن لم تفهم كل ما هو مذكور فيه، كل ما عليك هو إدخاله ومحاولة فهم التعليقات والشروحات: # لعبة الحياة import random, time, copy WIDTH = 60 HEIGHT = 20 # إنشاء قوائم الخلايا nextCells = [] for x in range(WIDTH): column = [] # إنشاء عمود جديد for y in range(HEIGHT): if random.randint(0, 1) == 0: column.append('#') # إضافة خلية حية else: column.append(' ') # إضافة خلية ميتة nextCells.append(column) # nextCells هي قائمة تتألف من قوائم للأعمدة while True: # حلقة البرنامج الرئيسية print('\n\n\n\n\n') # فصل الخطوة أو الجيل القادم بأسطر فارغة currentCells = copy.deepcopy(nextCells) # طباعة الخلايا الحالية على الشاشة for y in range(HEIGHT): for x in range(WIDTH): print(currentCells[x][y], end='') # طباعة # أو فراغ print() # طباعة سطر جديد في نهاية السطر # حساب الخطوة أو الجيل القادم اعتمادًا على القيم الحالية للخلايا for x in range(WIDTH): for y in range(HEIGHT): # الوصول إلى إحداثيات الخلايا المجاورة # `% WIDTH` يضمن أن leftCoord سيكون بين 0 و WIDTH - 1 leftCoord = (x - 1) % WIDTH rightCoord = (x + 1) % WIDTH aboveCoord = (y - 1) % HEIGHT belowCoord = (y + 1) % HEIGHT # إحصاء عدد الخلايا المجاورة numNeighbors = 0 if currentCells[leftCoord][aboveCoord] == '#': numNeighbors += 1 # الخلية في الركن العلوي الأيسر حية if currentCells[x][aboveCoord] == '#': numNeighbors += 1 # الخلية في الأعلى حية if currentCells[rightCoord][aboveCoord] == '#': numNeighbors += 1 # الخلية في الركن العلوي الأيمن حية if currentCells[leftCoord][y] == '#': numNeighbors += 1 # الخلية على اليسار حية if currentCells[rightCoord][y] == '#': numNeighbors += 1 #الخلية على اليمين حية if currentCells[leftCoord][belowCoord] == '#': numNeighbors += 1 # الخلية في الركن السفلي الأيسر حية if currentCells[x][belowCoord] == '#': numNeighbors += 1 # الخلية في الأسفل حية if currentCells[rightCoord][belowCoord] == '#': numNeighbors += 1 # الخلية في الركن السفلي الأيمن حية # ضبط قيمة القيمة اعتمادًا على قواعد لعبة الحياة if currentCells[x][y] == '#' and (numNeighbors == 2 or numNeighbors == 3): # الخلايا التي لها خلايا جارة حية عددها 2 أو 3 nextCells[x][y] = '#' elif currentCells[x][y] == ' ' and numNeighbors == 3: # الخلايا الميتة التي لها 3 خلايا جارة حية nextCells[x][y] = '#' else: # كل ما بقي يكون ميتًا أو سيمين nextCells[x][y] = ' ' time.sleep(1) # التوقف لمدة ثانية لتجنب تأثير الوموض المزعج لنلقي نظرةً على الشيفرة سطرًا بسطر بدءًا من الأعلى: # لعبة الحياة import random, time, copy WIDTH = 60 HEIGHT = 20 استوردنا بدايةً الوحدات التي تحتوي على الدوال التي سنحتاج إليها، تحديدًا الدوال random.randint()‎ و time.sleep()‎ و copy.deepcopy()‎. # إنشاء قوائم الخلايا nextCells = [] for x in range(WIDTH): column = [] # إنشاء عمود جديد for y in range(HEIGHT): if random.randint(0, 1) == 0: column.append('#') # إضافة خلية حية else: column.append(' ') # إضافة خلية ميتة nextCells.append(column) # nextCells هي قائمة تتألف من قوائم للأعمدة أول خطوة من إنشاء خلايا ذاتية السلوك هي خطوة (أو «جيل») عشوائية تمامًا. سنحتاج إلى إنشاء قائمة تضم قوائم لتخزين السلاسل النصية '#' و ' ' التي تمثل الخلايا الحية والميتة، وسيدل مكانها في قائمة القوائم على مكانها في الشاشة، فكل قائمة داخلية تمثل عمودًا من الخلايا، واستدعاؤنا للدالة random.randint(0, 1)‎ سيعطي الخلية احتمال 50% أن تكون حية و 50% أن تكون ميتة. سنضع قائمة القوائم في متغير اسمه nextCells، لأن أول خطوة ستجريها في حلقة البرنامج الرئيسية هي نسخ nextCells إلى currentCells. ستبدأ إحداثيات محور السينات X من 0 في الأعلى وستزداد نحو اليمين، بينما ستبدأ إحداثيات محور العينات Y من 0 أيضًا في الأعلى وستزداد نحو الأسفل، أي أن nextCells[0][0]‎ ستمثل الخلية في الركن العلوي الأيسر من الشاشة، بينما nextCells[1][0]‎ ستمثل الخلية التي على يمينها، و nextCells[0][1]‎ ستمثل الخلية التي تدنوها. while True: # حلقة البرنامج الرئيسية print('\n\n\n\n\n') # فصل الخطوة أو الجيل القادم بأسطر فارغة currentCells = copy.deepcopy(nextCells) كل دورة من حلقة تكرار البرنامج الرئيسية ستمثل خطوة أو جيلًا من الخلايا ذاتية السلوك التي لدينا. وفي كل دورة سننسخ قيمة nextCells إلى currentCells ثم نطبع currentCells على الشاشة، ثم سنستخدم الخلايا الموجودة في currentCells لحساب الخلايا التي ستكون في nextCells. # طباعة الخلايا الحالية على الشاشة for y in range(HEIGHT): for x in range(WIDTH): print(currentCells[x][y], end='') # طباعة # أو فراغ print() # طباعة سطر جديد في نهاية السطر حلقات for المتشعبة تعني أننا سنطبع سطرًا كاملًا من الخلايا على الشاشة، ثم يكون متبوعًا بسطر فارغ، ثم نكرر العملية لكل سطر في nextCells. # حساب الخطوة أو الجيل القادم اعتمادًا على القيم الحالية للخلايا for x in range(WIDTH): for y in range(HEIGHT): # الوصول إلى إحداثيات الخلايا المجاورة # `% WIDTH` يضمن أن leftCoord سيكون بين 0 و WIDTH - 1 leftCoord = (x - 1) % WIDTH rightCoord = (x + 1) % WIDTH aboveCoord = (y - 1) % HEIGHT belowCoord = (y + 1) % HEIGHT ثم سنسنعمل حلقتي for متشعبتين لحساب كل خلية للخطوة أو الجيل القادم، ولمّا كانت حالة الخلية إن كانت ستحيى أم ستموت في الجيل القادم معتمدةً على جاراتها من الخلايا، فعلينا أولًا حساب فهرس الخلايا التي على يسارها ويمينها وأعلاها وأدناها. عامل باقي القسمة % يجري عملية «التفاف للسطر»، فالجار الأيسر لخلية موجودة في العمود الأيسر سيكون ‎0 - 1 أو ‎-1، والالتفاف العمود إلى فهرس العمود الأيمن 59 فسنحسب ‎(0 - 1) % WIDTH، ولأن قيمة WIDTH هي 60 فستكون نتيجة التعبير هي 59. يمكن فعل المثل بالنسبة إلى الخلايا الجارة التي تعلو وتدنو وعلى يمين الخلية الحالية. # إحصاء عدد الخلايا المجاورة numNeighbors = 0 if currentCells[leftCoord][aboveCoord] == '#': numNeighbors += 1 # الخلية في الركن العلوي الأيسر حية if currentCells[x][aboveCoord] == '#': numNeighbors += 1 # الخلية في الأعلى حية if currentCells[rightCoord][aboveCoord] == '#': numNeighbors += 1 # الخلية في الركن العلوي الأيمن حية if currentCells[leftCoord][y] == '#': numNeighbors += 1 # الخلية على اليسار حية if currentCells[rightCoord][y] == '#': numNeighbors += 1 #الخلية على اليمين حية if currentCells[leftCoord][belowCoord] == '#': numNeighbors += 1 # الخلية في الركن السفلي الأيسر حية if currentCells[x][belowCoord] == '#': numNeighbors += 1 # الخلية في الأسفل حية if currentCells[rightCoord][belowCoord] == '#': numNeighbors += 1 # الخلية في الركن السفلي الأيمن حية لتقرير إن كانت الخلية الموجودة في nextCells[x][y]‎ ستكون حيةً أو ميتةً فنحتاج إلى إحصاء عدد الخلايا الحية المجاورة للخلية currentCells[x][y]‎، والسلسلة السابقة من تعابير if الشرطية تتحقق من الجارات الثمانية المجاورة للخلية، وتضيف القيمة 1 للمتغير numNeighbors لكل خلية حية. # ضبط قيمة القيمة اعتمادًا على قواعد لعبة الحياة if currentCells[x][y] == '#' and (numNeighbors == 2 or numNeighbors == 3): # الخلايا التي لها خلايا جارة حية عددها 2 أو 3 nextCells[x][y] = '#' elif currentCells[x][y] == ' ' and numNeighbors == 3: # الخلايا الميتة التي لها 3 خلايا جارة حية nextCells[x][y] = '#' else: # كل ما بقي يكون ميتًا أو سيمين nextCells[x][y] = ' ' time.sleep(1) # التوقف لمدة ثانية لتجنب تأثير الوموض المزعج ثم بمعرفتنا لعدد الخلايا الجارة الحية للخلية currentCells[x][y]‎، فيمكننا ضبط قيمة nextCells[x][y]‎ إلى '#' أو ' '. وبعد المرور على جميع إحداثيات x و y فسيتوقف التنفيذ لبرهة باستدعاء time.sleep(1)‎ ثم سيكمل تنفيذ البرنامج في بداية حلقة التكرار مجددًا. هنالك عدد من الأنماط للخلايا لها أسماء مثل «الطائرة الشراعية» أو «الطائرة ذات المروحة» أو «سفينة الفضاء الثقيلة». نمط «الطائرة الشراعية» هو النمط الذي رأيته في الشكل 8 وهو «يتحرك» قطريًا كل أربع خطوات. يمكنك إنشاء «طائرة شراعية» بتبديل السطر الآتي في برنامج conway.py: if random.randint(0, 1) == 0: إلى هذا السطر: if (x, y) in ((1, 0), (2, 1), (0, 2), (1, 2), (2, 2)): الخلاصة القوائم هي نوع بيانات مفيد يسمح لك بكتابة شيفرات تتعامل مع قيم متعددة مخزنة في متغير واحد. سنرى برامج في مقالات هذه السلسلة كان من المستحيل برمجتها دون الاعتماد على القوائم. القوائم هي نوع من أنواع البيانات المتسلسلة القابلة للتعديل. أي أن محتوياتها قد تتعدل برمجيًا. بينما تكون الصفوف tuple والسلاسل النصية من أنواع البيانات المتسلسلة لكنها غير قابلة للتعديل. يمكن إعادة كتابة قيمة متغير يحتوي على سلسلة نصية أو صف بقيمة أخرى، لكن هذا لا يكافئ تعديل قيمة السلسلة النصية أو الصف في مكانها، مثلما تفعل التوابع append()‎ أو remove()‎ على القوائم. لا تخزن المتغيرات قيم القوائم مباشرةً فيها، بل هي تشير إلى تلك القوائم، ومن المهم استيعاب هذه النقطة حين نسخ المتغيرات أو تمرير القوائم كوسائط إلى الدوال. ولأن القيمة التي ستنسخ هي مرجعية إلى القائمة وليست القائمة نفسها، فأي تعديل يجرى على القائمة سيؤثر عليها في كامل البرنامج؛ لكننا نستطيع استخدام الدالة copy()‎ أو deepcopy()‎ لنسخ القوائم ثم إجراء تعديلات عليها لا تؤثر على القائمة الأصلية. مشاريع تدريبية لكي تتدرب، اكتب برامج لتنفيذ المهام الآتية. افصل بفاصلة لنقل لدينا القائمة الآتية: spam = ['apples', 'bananas', 'tofu', 'cats'] اكتب دالة تأخذ قائمةً كمعامل وتعيد سلسلةً نصيةً فيها كل عناصر تلك القائمة مفصولٌ بينها بفاصلة ثم فراغ، وأضف الكلمة and قبل آخر عنصر. فلو كانت لدينا القائمة السابقة فستعيد الدالة القيمة 'apples, bananas, tofu, and cats'؛ لكن يجب أن تعمل دالتك مع جميع القيم. تذكر أن تجرب الدالة على قائمة فارغة []. سلسلة رمي القطع النقدية سنجري تجربة بسيطة. إذا رمينا قطعة نقدية مئة مرة، وكتبنا الحرف H لكل رأس والحرف T لكل نقش، فسيكون لدينا قائمة تشبه T T T T H H H H T T. إذا طلبنا من كائن بشري (أهلًا بك أخي البشري ? ) أن يكتب عشوائيًا نتائج مئة رمية لقطعة نقد، فستحصل على شيء يشبه H T H T H H T H T T والذي يبدو عشوائيًا إذا نظر كائن بشري إليه، لكنه لا يمثل سلسلة عشوائية رياضيًا. فلن يكتب الكائن البشري سلسلة من ستة رؤوس أو ستة نقوش متتالية، مع أن ذلك ممكن رياضيًا لو كانت عملية رمي القطع النقدية عشوائيًا فعليًا. فمن المتوقع أن تكون التوقعات العشوائية للكائنات البشرية تعيسةً (آسف أخي البشري، لكنها الحقيقة ? ). اكتب برنامجًا يعرف كم مرة ظهرت سلسلة من ستة رؤوس أو ستة نقوش في قائمة مولدة عشوائيًا. سيقسم برنامجك هذه التجربة إلى قسمين: القسم الأول سيولد قائمة عشوائية من الرؤوس والنقوش، والقسم الثاني سيتحقق من سلسلةً متتاليةً من الروؤس أو النقوش موجودة في تلك القائمة. أجرِ هذه التجربة 10,000 مرة لكي يكون تعرف النسبة التي تحتوي فيها قائمة الرؤوس والنقوش على ستة رؤوس متتالية أو ستة نقوش متتالية. أذكرك أن الدالة random.randint(0, 1)‎ ستعيد القيمة 0 بنسبة 50% والقيمة 1 بنسبة 50%. يمكنك أن تستفيد من القالب الآتي: import random numberOfStreaks = 0 for experimentNumber in range(10000): # الشيفرة التي ستولد قائمة من 100 قيمة عشوائية لعملية رمي القطعة النقدية # الشيفرة التي ستتحقق من ظهور 6 رؤوس أو 6 نقوش متتالية print('Chance of streak: %s%%' % (numberOfStreaks / 100)) تذكر أن الرقم الناتج تقريبي عملي، لكن حجم العينة (عشرة آلاف) مناسب؛ لكن إذا كنت تعرف بعض المبادئ الأساسية في الاحتمالات والإحصاء الرياضي فستعرف الإجابة الدقيقة دون الحاجة إلى كتابة البرنامج السابق، لكن من الشائع أن تكون معرفة المبرمجين بالرياضيات تعيسة (على عكس المتوقع). صورة حرفية لنقل أن لدينا قائمة تضم قوائم أخرى، وكل قيمة في القوائم الداخلية هي حرف واحد كما يلي: grid = [['.', '.', '.', '.', '.', '.'], ['.', 'O', 'O', '.', '.', '.'], ['O', 'O', 'O', 'O', '.', '.'], ['O', 'O', 'O', 'O', 'O', '.'], ['.', 'O', 'O', 'O', 'O', 'O'], ['O', 'O', 'O', 'O', 'O', '.'], ['O', 'O', 'O', 'O', '.', '.'], ['.', 'O', 'O', '.', '.', '.'], ['.', '.', '.', '.', '.', '.']] تخيل أن العنصر grid[x][y]‎ هو المحرف الموجود في الإحداثيات x و y «للصورة» التي سنرسمها عبر الأحرف. مبدأ الإحداثيات (0, 0) هو الركن العلوي الأيسر، وستزداد إحداثيات x بالذهاب نحو اليمين، وإحداثيات y نحو الأسفل. انسخ الشبكة السابقة واكتب شيفرة لطباعة الشكل الآتي منها: ..OO.OO.. .OOOOOOO. .OOOOOOO. ..OOOOO.. ...OOO... ....O.... تلميحة: ستحتاج إلى حلقة تكرار داخل حلقة تكرار، لكي تطبع grid[0][0]‎ ثم grid[1][0]‎ ثم grid[2][0]‎ وهلم جرًا إلى أن تصل إلى grid[8][0]‎؛ ثم ستنتهي من أول صف وتطبع سطرًا جديدًا، ثم تطبع grid[0][1]‎ ثم grid[1][1]‎ ثم grid[2][1]‎ …إلخ. وآخر عنصر سيطبعه برنامجك هو grid[8][5]‎. تذكر أن تمرر الوسيط المسمى end إلى الدالة print()‎ إذا لم تكن تريد طباعة سطر جديد بعد كل استدعاء للدالة print()‎. ترجمة -بتصرف- للفصل LISTS من كتاب Automate the Boring Stuff with Python. اقرأ أيضًا المقال السابق: الدوال في لغة بايثون Python المقال السابق: بنى التحكم في لغة بايثون Python أنواع البيانات والعمليات الأساسية في لغة بايثون تعلم لغة بايثون النسخة العربية الكاملة لكتاب البرمجة بلغة بايثون
  5. تعرفت في المقالات السابقة على الدوال print()‎ و input()‎ و len()‎، وأصبحت تعرف بتوفير بايثون عدّة دوال مضمنة في اللغة built-in functions مثلها، لكنك تستطيع كتابة دوال خاصة بك أيضًا. يمكنك القول أن الدالة هي برنامج صغير داخل برنامجك، ولكي نفهم سويةً كيف تعمل الدوال فنحتاج إلى إنشاء واحدة. أدخل الشيفرة الآتية في المحرر لديك وسم الملف باسم helloFunc.py: ➊ def hello(): ➋ print('Howdy!') print('Howdy!!!') print('Hello there.') ➌ hello() hello() hello() أول سطر هو عبارة def التي تعرف دالةً باسم hello()‎، والشيفرة التي تلي عبارة def هي جسم الدالة ➋، الذي يحتوي على الشيفرة التي ستنفذ حين استدعاء call الدالة، وليس حين تعريف الدالة. الأسطر الثلاثة التي تحتوي على hello()‎ ➌ هي استدعاءات الدالة، وعملية استدعاء الدالة هي كتابة اسمها متبوعًا بقوسين، وقد تمرر إليها بعض الوسائط arguments بين القوسين. حينما يصل تنفيذ البرنامج إلى استدعاء هذه الدوال، فسينتقل التنفيذ إلى أول سطر في الدالة ويبدأ بتنفيذ الشيفرات الموجودة فيها حتى يصل إلى نهايتها، وحينها يعود التنفيذ إلى السطر البرمجي الذي استدعى الدالة، ثم يكمل البرنامج عمله كالمعتاد. ولأننا استدعينا الدالة hello()‎ ثلاث مرات، فإن الشيفرة الموجودة داخل الدالة hello()‎ ستنفذ ثلاث مرات، وحينما تشغِّل البرنامج السابق سيظهر لديك: Howdy! Howdy!!! Hello there. Howdy! Howdy!!! Hello there. Howdy! Howdy!!! Hello there. أحد الأغراض الأساسية من تعريف الدوال هو تجميع الشيفرات مع بعضها والتي يمكن أن تنفذ عدة مرات، فلو لم تكن لدينا الدالة السابقة فسنحتاج إلى نسخ ولصق الشيفرة عدة مرات كما يلي: print('Howdy!') print('Howdy!!!') print('Hello there.') print('Howdy!') print('Howdy!!!') print('Hello there.') print('Howdy!') print('Howdy!!!') print('Hello there.') هنالك قاعدة عامة تقول أن عليك تفادي تكرار الشيفرات نفسها قدر الإمكان، لأنك إذا قررت مستقبلًا أن تجري تغييرًا عليها -كأنك وجدت علّة وحللتها مثلًا- فعليك أن تجري هذا التغيير في كل مكان نسخت إليه تلك الدالة. وكلما زادت خبرتك البرمجة وجدتَ نفسك تقلل من تكرار الشيفرات، مما يجعل برامجك أقصر وأسهل للقراءة وأسهل في التعديل والتحديث. عبارة def مع معاملات تذكر حينما كنت تستدعي الدالة print()‎ أو len()‎ كنت تمرر إليها قيمًا تسمى بالوسائط arguments، وذلك بكتابتها بين القوسين. يمكنك أيضًا أن تعرف دوالك التي تقبل وسائط، وجرب هذا المثال في ملف باسم helloFunc2.py: ➊ def hello(name): ➋ print('Hello, ' + name) ➌ hello('Alice') hello('Bob') ستبدو المخرجات كما يلي حين تشغيل البرنامج السابق: Hello, Alice Hello, Bob لاحظ أن تعريفنا للدالة hello()‎ في هذا المثال يحتوي على معامل باسم name ➊. المعاملات parameters هي المتغيرات التي تحتوي على قيمة الوسائط arguments. أي حينما نستدعي دالةً مع وسائط arguments فإن قيمة تلك الوسائط ستكون مخزنة في المعاملات parameters. حين استدعاء الدالة hello()‎ لأول مرة سنمرر إليها القيمة 'Alice' ➌ وسينتقل التنفيذ إلى داخل الدالة، وستُضبَط قيمة المعامل name إلى القيمة 'Alice' تلقائيًا، ومن ثم ستطبع هذه القيمة باستخدام الدالة print()‎ ➋. أحد الأمور التي من المهم تذكرها حول المعاملات هي أن القيمة المخزنة فيها ستنسى حين إنتهاء تنفيذ الدالة، فلو كتبت print(name)‎ بعد استدعاء hello('Bob')‎ في البرنامج السابق، فسيظهر لك الخطأ NameError لعدم وجود متغير باسم name، وذلك لأن برنامجنا سيحذف المتغير name بعد إنتهاء استدعاء الدالة hello('Bob')‎ ولذا سنشير في print(name)‎‎ إلى المتغير name الذي لن يكون موجودًا. هذا يشبه كثيرًا كيف تحذف قيمة المتغيرات في برامجنا السابقة من الذاكرة حين انتهاء تنفيذها. وسنتحدث عن سبب حدوث ذلك تفصيلًا لاحقًا في هذا المقال حينما نتتحدث عن النطاق المحلي local scope. التعريف والاستدعاء والتمرير والوسائط والمعاملات! كثرت علينا المصطلحات الجديدة كالتعريف define والاستدعاء call والتمرير pass والوسائط arguments والمعاملات parameters. لننظر سويةً إلى الشيفرة الآتية لمراجعتها: ➊ def sayHello(name): print('Hello, ' + name) ➋ sayHello('Abdullatif') تعريف الدالة يعني إنشاءها، وكما في عبارة الإسناد spam = 42 التي تنشِئ المتغير spam سنستعمل العبارة def لتعريف الدالة sayHello()‎ ➊. السطر الذي فيه sayHello('Abdullatif')‎ ➋ يستدعي الدالة التي أنشأناها، مما ينقل تنفيذ البرنامج إلى بداية الشيفرة الموجودة داخل الدالة، وسطر الاستدعاء السابق يمرر السلسلة النصية 'Abdullatif' إلى الدالة، والقيمة التي تمرر إلى الدالة تسمى وسيطًا، وسيُسند الوسيط 'Abdullatif' إلى المتغير المحلي المسمى name، وتسمى المتغيرات التي تحمل قيمة الوسائط الممررة إلى الدالة بالمعاملات. من السهل الخلط بين المصطلحات السابقة، لكن حاول أن تستوعبها جيدًا لكي تفهم بقية المقال بسهولة. القيم المعادة وعبارة return عندما تستدعي الدالة len()‎ وتمرر إليها وسيطًا مثل 'Hello' فستكون القيمة الناتجة من الدالة هي الرقم 5، الذي يمثل طول السلسلة النصية التي مررتها إليها. يمكننا القول عمومًا أن الناتج إحدى الدوال يسمى بالقيمة المعادة return value من تلك الدالة. حينما تنشِئ دالةً باستخدام العبارة def فيمكنك أن تحدد ما هي القيمة المعادة من الدالة باستخدام العبارة return. تتألف عبارة return من: الكلمة المحجوزة return قيمة أو تعبير برمجي يجب أن تعيدها الدالة حين استخدام تعبير برمجي مع عبارة return فستكون القيمة المعادة هي ناتج ذاك التعبير. فمثلًا البرنامج الآتي يعرف دالةً تعيد سلسلةً نصيةً مختلفةً اعتمادًا على الرقم المُمرَّر إليها كوسيط. احفظ الشيفرة الآتية في ملف باسم magic8Ball.py: ➊ import random ➋ def getAnswer(answerNumber): ➌ if answerNumber == 1: return 'It is certain' elif answerNumber == 2: return 'It is decidedly so' elif answerNumber == 3: return 'Yes' elif answerNumber == 4: return 'Reply hazy try again' elif answerNumber == 5: return 'Ask again later' elif answerNumber == 6: return 'Concentrate and ask again' elif answerNumber == 7: return 'My reply is no' elif answerNumber == 8: return 'Outlook not so good' elif answerNumber == 9: return 'Very doubtful' ➍ r = random.randint(1, 9) ➎ fortune = getAnswer(r) ➏ print(fortune) حين يبدأ تنفيذ البرنامج السابق فستسورد بايثون الوحدة random ➊، ثم ستعرف الدالة getAnswer()‎ ➋، ولأننا عرفنا الدالة ولم نستدعها فلن تنفذ الشيفرة الموجودة داخلها، بل سينتقل التنفيذ مباشرةً إلى استدعاء الدالة random.randint()‎ التي مررنا إليها وسيطين هما 1 و 9 ➍ وسيعاد منها عدد عشوائي بين 1 و 9 (بما في ذلك الرقمين 1 و 9) وتخزن هذه القيمة في المتغير r. تستدعى الدالة getAnswer()‎ مع تمرير الوسيط r ➎، وينتقل التنفيذ إلى بداية الدالة getAnswer()‎ ➌، وستخزن قيمة الوسيط r في المعامل answerNumber، ثم اعتمادًا على القيمة الموجودة في answerNumber فستعيد الدالة إحدى السلاسل النصية المعرفة مسبقًا، ومن ثم سيعود التنفيذ إلى النقطة التي استدعيت فيها الدالة getAnswer()‎ ➎ وستسند السلسلة النصية المعادة إلى المتغير ذي الاسم fortune، الذي سيمرر بدوره إلى الدالة print()‎ ➏ ويطبع على الشاشة. لاحظ أنك تستطيع تمرير القيم المعادة من الدوال كوسيط مباشرةً إلى دوال أخرى، لذا يمكنك أن تختصر الأسطر الثلاثة الآتية: r = random.randint(1, 9) fortune = getAnswer(r) print(fortune) إلى السطر المكافئ: print(getAnswer(random.randint(1, 9))) تذكر أن التعابير البرمجية تتألف من قيم وعوامل، ويمكن استخدام استدعاءات الدوال في تعبير برمجي لأن الاستدعاء سيؤول إلى قيمة معادة من تلك الدالة. القيمة None هنالك قيمة في بايثون تسمى None وهي تمثل عدم وجود قيمة. والقيمة None هي القيمة الوحيدة لنوع البيانات NoneType، وقد تسمي لغات البرمجة الأخرى هذه القيمة بالاسم null أو nil أو undefined. وكما في القيم المنطقية True و False فيجب أن نكتب None بحرف N كبير. يمكن أن تفيد هذه القيمة-التي-ليس-لها-قيمة حين الحاجة إلى استعمال قيمة لا تمثل شيئًا، فإحدى استخدامات None مثلًا هي القيمة المعادة من الدالة print()‎ التي تطبع نصًا على الشاشة، لكنها لا تعيد أي قيمة على عكس دوال أخرى مثل len()‎ أو input()‎، ولأن من المفترض أن يكون هنالك قيمة ناتجة لجميع استدعاءات الدوال في بايثون فاستدعاء print()‎ سيعيد القيمة None. لنجرب السطرين الآتيين في الصدفة التفاعلية لنرى ذلك: >>> spam = print('Hello!') Hello! >>> None == spam True تضيف بايثون العبارة return None وراء الكواليس لأي دالة لا يكون لها عبارة return محددة، وهذا ما يشبه كيف تحتوي حلقات التكرار while و for على عبارة continue ضمنية في نهايتها. ستعاد القيمة None أيضًا إن استخدام عبارة return دون قيمة، أي كتبت الكلمة المفتاحية return كما هي. وسطاء الكلمات المفتاحية والدالة print()‎ تعرف أغلبية الوسائط بموضعها حين استدعاء الدالة، فمثلًا الاستدعاء random.randint(1, 10)‎ مختلف عن الاستدعاء random.randint(10, 1)‎، فحينما نستدعي الدالة random.randint(1, 10)‎ فستعيد لنا رقمًا صحيحًا بين 1 و 10 لأن أول وسيط هو الحد الأدنى من المجال والوسيط الثاني هو الحد الأقصى، بينما يسبب استدعاء random.randint(10, 1)‎ خطأً. لكن بدلًا من تعريف قيم الوسائط عبر موضعها، يمكن أن تعرف وسطاء الكلمات المفتاحية keyword arguments بوضع كلمة مفتاحية قبلها حين استدعاء الدالة، وتستخدم وسطاء الكلمات المفتاحية عادةً للمعاملات الاختيارية optional parameters. فمثلًا تمتلك الدالة print()‎ معاملين اختياريين هما end و sep لضبط ما الذي سيطبع بعد نهاية طبع الوسائط الممررة إليها وما الذي سيطبع بين تلك الوسائط على التوالي. إذا شغلنا برنامجًا يحتوي على الشيفرة الآتية: print('Hello') print('World') فسيكون الناتج كما يلي: Hello World لاحظ أن السلسلتين النصيتين المطبوعتين مفصولتان بسطر وذلك لأن الدالة print()‎ تضيف تلقائيًا محرف السطر الجديد newline character في نهاية السلسلة النصية التي تمرر إليها كوسيط. إلا أننا نستطيع ضبط قيمة الوسيط end لتغيير محرف السطر الجديد إلى سلسلة نصية مختلفة، فمثلًا إذا كتبت الشيفرة الآتية: print('Hello', end='') print('World') فسيكون الناتج: HelloWorld سيطبع الناتج في سطر وحيد لعدم وجود محرف السطر الجديد بعد السلسلة النصية 'Hello' لأننا مررنا سلسلة نصية فارغة، وهذا ما يفيدك إن أردت تعطيل الإضافة التلقائية للسطر الجديد في نهاية كل استدعاء للدالة print()‎. إذا مررت عدة سلاسل نصية إلى الدالة print()‎ فستفصل الدالة بينها تلقائيًا بفراغ واحد. جرب إدخال السطر الآتي في الصدفة التفاعلية: >>> print('cats', 'dogs', 'mice') cats dogs mice يمكنك تبديل السلسلة النصية التي تفصل بين الوسائط التي ستطبع باستخدام الوسيط sep وتمرير سلسلة نصية مختلفة إليه، جرّب ذلك في الصدفة التفاعلية: >>> print('cats', 'dogs', 'mice', sep=',') cats,dogs,mice يمكنك إضافة وسائط مفتاحية في الدوال التي تكتبها أيضًا، لكن عليك أن تتعلم أولًا عن القوائم list والقواميس dictionary التي سنشرحها في مقالات لاحقة. لكن كل ما عليك معرفته الآن هو أن بعض الدوال لها معاملات اختيارية ويكون لها مفاتيح يمكن الوصول إليها لضبط قيمتها حين استدعاء الدالة. مكدس الاستدعاء Call Stack تخيل أنك تدردش مع أحد أصدقائك، فستتحدث عن صديقك محمد، ثم تتذكر قصة عن زميلك في العمل عبد الحميد، ثم تتذكر شيئًا عن ابن عمك جميل، وحينما تنتهي قصتك عن جميل تعود إلى حديثك عن عبد الحميد، ثم تعود وتتحدث عن محمد، وبعد ذلك تتذكر أخاك بشر، وتقص قصة عن بشر، ثم تعود وتكمل القصة الأصلية لمحمد. تتبع محادثتك بنيةً شبيهةً بالمكدس stack، كما في الشكل الموالي، التي يكون فيها الموضوع الحالي في أعلى المكدس. مكدس القصص في دردشتك. وكما في محادثتك السابقة، عملية استدعاء دالة لا تؤدي إلى نقل التنفيذ إلى بداية الدالة التي جرى استدعاؤها، بل تتذكر بايثون ما هو السطر الذي استدعى تلك الدالة لكي تستطيع توفير القيمة المعادة من تلك الدالة إليه. وإذا استدعت تلك الدالة دوالًا أخرى فستعاد القيم الناتجة عن تلك الدوال إلى أماكن استدعائها الأصلية أولًا. لنفهم ما يحدث بالتفصيل سنجرب المثال الآتي بعد حفظه في ملف باسم abcdCallStack.py: def a(): print('a() starts') ➊ b() ➋ d() print('a() returns') def b(): print('b() starts') ➌ c() print('b() returns') def c(): ➍ print('c() starts') print('c() returns') def d(): print('d() starts') print('d() returns') ➎ a() سينتج البرنامج ما يلي حين تشغيله: a() starts b() starts c() starts c() returns b() returns d() starts d() returns a() returns أعرف أن الفقرة الآتية متداخلة، لكن حاول أن تركز معي فيها: حين استدعاء a()‎ ➎ فستستدعي b()‎ ➊ التي بدورها ستستدعي c()‎ ➌. والدالة c()‎ لا تستدعي غيرها بل تعرض العبارة c() starts ➍ و c() returns قبل أن يعود التنفيذ إلى السطر الذي استدعاها في b()‎ ➌. بعد أن يعود التنفيذ إلى الشيفرة في b()‎ التي استدعت c()‎ فستنتهي الدالة b()‎ وتطبع b() returns ثم يعود التنفيذ إلى السطر الذي استدعى b()‎ في a()‎ ➊. سيستمر التنفيذ في الدالة a()‎ وستستدعى الدالة d()‎، والتي تشبه الدالة c()‎ في كونها لا تستدعي دالةً غيرها بل تطبع d()‎ starts و d() returns قبل أن تعود إلى السطر الذي استدعاها في a()‎ ثم سيكمل التنفيذ من هناك، وسيطبع آخر سطر من a()‎ العبارة a()‎ returns قبل أن ينتهي تنفيذ الدالة a()‎ ونصل إلى نهاية البرنامج. مكدس الاستدعاء call stack هو الآلية التي تستعملها بايثون لتذكر أين سيعود التنفيذ بعد انتهاء تنفيذ استدعاء كل دالة. لا يخزن مكدس الاستدعاء في متغير في برنامجك وإنما تتولى بايثون أمره خلف الكواليس. فحينما يستدعي برنامجك دالةً فستنشِئ بايثون كائن إطار frame object فوق مكدس الاستدعاء، وتخزن كائنات الإطار frame objects رقم السطر الذي استدعى الدالة لكي تعرف بايثون أين يجب أن تعيد القيمة الناتجة من ذاك الاستدعاء. إذا استدعيت دالة أخرى فستضع بايثون كائن إطار آخر في المكدس فوق السابق. بعد إعادة استدعاء الدالة فستحذف بايثون كائن الإطار من أعلى المكدس وتكمل التنفيذ من السطر المخزن في ذاك الكائن. لاحظ أن كائنات الإطار تضاف وتحذف من أعلى المكدس وليس من أي مكان آخر. يوضح الشكل الموالي حالة مكدس الاستدعاء في البرنامج abcdCallStack.py حين استدعاء والعودة من كل دالة: كائنات الإطار لمكدس الاستدعاء في كل مرحلة من مراحل تنفيذ البرنامج abcdCallStack.py. يمثل أعلى مكدس الاستدعاء الدالة التي يجري تنفيذها حاليًا، وحينما يكون المكدس فارغًا فسيكون التنفيذ في متن البرنامج وخارج جميع الدوال. لا حاجة إلى تعلم المزيد حول مكدس الاستدعاء لكي تكتب برامجك، لأنه يدخل في تفاصيل تقنية عميقة لا داعي لها حاليًا. من الكافي أن تفهم أن الدوال ستعيد القيم إلى السطر الذي استدعيت فيه؛ لكني آثرت شرح مكدس الاستدعاء هنا لأن فهمه سيسهل استيعاب مفاهيم المجالات العامة والمحلية التي سنشرحها في القسم الآتي. المجالات العامة والمحلية تكون المعاملات parameters والمتغيرات الموجودة داخل إحدى الدوال ضمن مجال محلي local scope. أما المتغيرات التي تسند قيمتها خارج جميع الدوال تكون موجودة في المجال العام global scope. وبالتالي يسمى المتغير الموجود في مجال محلي بالمتغير المحلي local variable، بينما يسمى المتغير الموجود في المجال العام بالمتغير العام global variable؛ ويجب أن يكون المتغير عامًا أو محليًا، ولا يمكنه أن يكون كلاهما معًا. يمكنك تخيل المجالات scopes على أنها حاوية للمتغيرات؛ فحينما ينتهي وجود أحد المجالات المحلية فستحذف جميع المتغيرات الموجودة فيه. لاحظ وجود مجال عام وحيد الذي سينُشَأ حينما يبدأ تنفيذ برنامجك وينتهي بإنتهاء تنفيذه. يمكنك التأكد من حذف المتغيرات في المجال العام بعد إنتهاء تنفيذ البرنامج بتجربة الوصول إلى المتغيرات من برنامج آخر. يُنشَأ المجال المحلي في كل مرة تستدعى فيها إحدى الدوال. فتكون جميع المتغيرات المسندة ضمن الدالة موجودة في المجال المحلي. وحين إعادة قيمة return من الدالة فسينتهي وجود المجال المحلي وستحذف قيمة تلك المتغيرات؛ ولن يتذكر برنامج قيمة المتغيرات المخزنة من الاستدعاء السابق للدالة. تخزن المتغيرات المحلية أيضًا في كانئات الإطار frame objects في مكدس الاستدعاء call stack. يهمنا معرفة المجال المستخدم لعدة أسباب: لا يمكن للشيفرات الموجودة في المجال العام أن تستعمل أي متغيرات محلية لكن يمكن للشيفرات في المجال المحلي الوصول إلى المتغيرات العامة الشيفرة في المجال المحلي لإحدى الدوال لا تستطيع استخدام أي متغيرات موجودة في المجال المحلي لدالة أخرى يمكنك استخدام الاسم نفسه لمتغيرات مختلفة على أن تكون موجودة في مجالات مختلفة. أي يمكن أن يسمى متغير محلي بالاسم spam مثلًا ويكون هنالك متغير عام بالاسم spam أيضًا. السبب في امتلاك بايثون لمجالات مختلفة بدل من جعل جميع المتغيرات عامة هو أن بعض المتغيرات تعدل من شيفرة معينة ضمن دالة ما، وتتفاعل هذه الدالة مع بقية البرنامج عبر المعاملات parameters الممررة إليها وعبر القيمة المعادة منها؛ وهذا ما يقلل عدد الأسطر البرمجية التي تتعامل مع متغير ما وتسبب مشكلة برمجية، فإذا كان يحتوي برنامجك على متغيرات عامة فقط وحصل خطأ بسبب ضبط أحد المتغيرات إلى قيمة خطأ فسيصعب كثيرًا تتبع المشكلة في برنامجك، فقد يكون عدد الأسطر البرمجية بالمئات أو الآلاف التي قد تستطيع تعديل قيمة هذا المتغير؛ أما لو كانت العلة البرمجية بسبب قيمة خطأ لمتغير محلي فستعرف تحديدًا ما هي الأسطر البرمجية المسؤولة عن ضبط تلك القيمة وستحل المشكلة بسهولة وسرعة. لا مشكلة في استخدام المتغيرات العامة في البرامج القصيرة سهولتها، لكنها من غير المستحسن الاعتماد على المتغيرات العامة حينما تكبر برامجك. لا يمكن استخدام المتغيرات المحلية في المجال العام أمعن النظر في البرنامج الآتي الذي سيتسبب بخطأ حين محاولة تشغيله: def spam(): ➊ eggs = 31337 spam() print(eggs) إذا جربت هذا البرنامج فسيبدو الناتج كما يلي: Traceback (most recent call last): File "C:/test1.py", line 4, in <module> print(eggs) NameError: name 'eggs' is not defined سيحدث الخطأ بسبب وجود المتغير eggs داخل المجال المحلي المنشأ من الدالة spam()‎ ➊. فبعد انتهاء تنفيذ الدالة spam فسيحذف المجال المحلي ولن يبقى هنالك أي متغير باسم eggs، وحينما يحاول البرنامج تشغيل السطر print(eggs)‎ فستعطيك بايثون خطأً تقول فيه أن المتغير eggs غير معرف، وهذا منطقي إذا فكرت مليًا بالأمر؛ فحينما يكون تنفيذ البرنامج في المجال العام فلا توجد أي مجالات محلية ولن تكون هنالك أي متغيرات محلية، وبالتالي لا يمكننا استخدام سوى المتغيرات العامة في المجال العام. لا يمكن استخدام المتغيرات المحلية في مجالات محلية أخرى ينشأ مجال محلي جديد في كل مرة تستدعى فيها إحدى الدوال، بما في ذلك حين استدعاء دالة ضمن دالة أخرى. انظر إلى المثال الآتي: def spam(): ➊ eggs = 99 ➋ olive() ➌ print(eggs) def olive(): steak = 101 ➍ eggs = 0 ➎ spam() حينما يبدأ تشغيل البرنامج فستستدعى الدالة spam()‎ ➎ وسينشأ مجال محلي، وسيضبط المتغير المحلي eggs ➊ إلى 99، ثم ستستدعى الدالة olive()‎ ➋، ثم سينشأ مجال محلي جديد؛ فمن الممكن أن تكون عدة مجالات محلية موجودة جنبًا إلى جنب. وسنضبط قيمة المتغير المحلي Steak إلى 101، وسننشِئ المتغير المحلي eggs -المختلف كليًا عن المتغير الذي يحمل نفس الاسم في المجال المحلي للدالة spam()‎- ونضبط قيمته إلى 0 ➍. حين إعادة الدالة olive()‎ فسيحذف المجال المحلي المنشأ بسبب استدعائها، بما في ذلك المتغير eggs الخاص بها. وسيكمل تنفيذ البرنامج في الدالة spam()‎ ليطبع لنا قيمة المتغير eggs ➌؛ ولأن المجال المحلي الخاص بالدالة spam()‎ ما يزال موجودًا فستكون قيمة eggs هي 99 كما ضبطناها سابقًا في ذلك المجال. خلاصة الكلام السابق كله هو أن المتغيرات المحلية الموجودة في إحدى الدوال مفصولة تمامًا عن المتغيرات المحلية في دالة أخرى. يمكن قراءة المتغيرات العامة من مجال محلي أمعن النظر في المثال الآتي: def spam(): print(eggs) eggs = 42 spam() print(eggs) حينما حاولنا طباعة المتغير eggs ضمن الدالة spam()‎ بحثت بايثون عن متغير أو معامل باسم eggs في الدالة spam()‎ لكنها لم تجد، فحينها ستعدّه إشارةً إلى المتغير العام eggs، ولهذا سيطبع البرنامج السابق القيمة 42 حين تنفيذه. المتغيرات المحلية والعامة التي تحمل الاسم نفسه من المقبول تمامًا من الناحية التقنية في بايثون استخدام نفس الاسم لمتغير في المجال العام وآخر في المجال المحلي؛ لكن لتسهيل مقروئية الشيفرة فحاول تجنب فعل ذلك. لترى ما سيحدث فجرب الشيفرة الآتية: def spam(): ➊ eggs = 'spam local' print(eggs) # 'spam local' def olive(): ➋ eggs = 'olive local' print(eggs) # 'olive local' spam() print(eggs) # 'olive local' ➌ eggs = 'global' olive() print(eggs) # 'global' سيظهر الناتج الآتي حينما تجرب تشغيل البرنامج السابق: olive local spam local olive local global هنالك ثلاثة متغيرات مختلفة في البرنامج، لكنها كلها مسماة eggs، وهي كما يلي: ➊ متغير باسم eggs موجود في المجال المحلي للدالة spam()‎. ➋ متغير باسم eggs موجود في المجال المحلي للدالة olive()‎. ➌ متغير باسم eggs موجود في المجال العام. ولأن هذه المتغيرات المختلفة لها نفس الاسم فسيكون من العسير تتبع أيها يستعمل الآن؛ لذا تجنب استخدام نفس الاسم لأكثر من متغير. العبارة البرمجية global إذا أردت تعديل قيمة متغير عام ضمن دالة، فعليك استخدام العبارة البرمجية global. فإذا كان لديك سطر يشبه global eggs في بداية إحدى الدوال فهذا سيخبر بايثون أن «المتغير eggs في هذه الدالة يشير إلى متغير عام، ولا حاجة إلى إ،شاء متغير محلي بهذا الاسم». فمثلًا جرب الشيفرة الآتية: def spam(): ➊ global eggs ➋ eggs = 'spam' eggs = 'global' spam() print(eggs) استدعاء الدالة print()‎ سيؤدي إلى إظهار الناتج الآتي: spam ولأننا صرحنا أن المتغير eggs هو عام global في بداية الدالة spam()‎ ➊، فحينما نضبط eggs إلى 'spam' ➋ فستجرى عملية الإسناد إلى المتغير eggs العام، ولن ينشأ أي متغير محلي. هنالك أربع قواعد لمعرفة إن كان المتغير في المجال المحلي أم العام: إذا كان المتغير مستخدمًا في المجال العام، أي خارج جميع الدوال، فسيكون متغيرًا عامًا دومًا. إذا كانت هنالك عبارة global في إحدى الدوال، فسيكون المتغير عامًا في تلك الدالة. خلاف ذلك إذا استخدم المتغير في عبارة الإسناد داخل دالة ما، فسيكون متغيرًا محليًا. لكن إن لم يستخدم ذاك المتغير في عبارة إسناد داخل الدالة فسيكون متغيرًا عامًا. لتأخذ فكرة أفضل عن هذه القواعد فاكتب البرنامج الآتي في محرر الشيفرات واحفظه وجربه: def spam(): ➊ global eggs eggs = 'spam' # this is the global def olive(): ➋ eggs = 'olive' # this is a local def Steak(): ➌ print(eggs) # this is the global eggs = 42 # this is the global spam() print(eggs) سيكون المتغير eggs في الدالة spam()‎ عامًا لوجود العبارة global في بداية الدالة ➊، وسيكون المتغير eggs محليًا في الدالة olive()‎ لاستعماله في عبارة إسناد في تلك الدالة ➋، وسيكون eggs عامًا في steak()‎ ➌ لعدم استخدامه في عبارة إسناد أو العبارة global. إذا شغلت البرنامج السابق فستكون النتيجة هي: spam كقاعدة عامة: سيكون المتغير في دالةٍ ما إما عامًا أو محليًا، ولا يمكن استخدام متغير محلي في دالة باسم eggs ثم استخدام متغير عام بنفس الاسم لاحقًا في الدالة ذاتها. إذا حاولت استخدام متغير محلي ضمن دالة قبل أن تسند قيمةً له كما في البرنامج الآتي، فستظهر لك رسالة خطأ: def spam(): print(eggs) # ERROR! ➊ eggs = 'spam local' ➋ eggs = 'global' spam() ستظهر رسالة الخطأ الآتية إذا حاولت تجربة البرنامج السابق: Traceback (most recent call last): File "C:/sameNameError.py", line 6, in <module> spam() File "C:/sameNameError.py", line 2, in spam print(eggs) # ERROR! UnboundLocalError: local variable 'eggs' referenced before assignment يظهر الخطأ لأن بايثون سترى عبارة إسناد للمتغير eggs ضمن الدالة spam()‎ ➊ وبالتالي ستعد المتغير على أنه محلي، لكننا نحاول طباعة قيمة eggs قبل إسناد أي قيمة له، أي أن المتغير المحلي eggs غير موجود، فسيظهر الخطأ ولن تستعمل بايثون المتغير العام eggs ➋. تعامل مع الدوال على أنها «صناديق سوداء» عادةً كل ما تحتاج إليه لاستخدام دالة هو معرفة ما هي المدخلات (أي ما هي المعاملات التي تأخذها) وما هي المخرجات؛ فلا حاجة إلى أن تثقل على نفسك بمعرفة كيف تعمل شيفرة تلك الدالة. فحاول أن تنظر إلى الدوال نظرة شاملة عالية المستوى، وبإمكانك معاملتها على أنها «صندوق أسود». هذه الفكرة أساسية في البرمجة الحديثة، وسترى عدة وحدات في المقالات اللاحقة من هذه السلسلة فيها دوال مكتوبة من مبرمجين آخرين، وصحيح أنك تستطيع النظر إلى الشيفرة المصدرية لها لكن لا حاجة إلى أن تفهم كيف تعمل لكي تستعملها؛ ولأن من المستحسن كتابة الدوال دون أن تتعامل مع المتغيرات العامة فلا تقلق حول تفاعل الدوال مع المتغيرات الموجودة في المجال العام لبرنامجك. التعامل مع الاستثناءات حينما يحدث خطأ -أو بتعبير أدق «استثناء» exception- في برنامجك فهذا يعني توقف عملية التنفيذ كلها. ولا تريد أن يحدث ذلك عمليًا في البرامج الحقيقية، وإنما تريد أن يحس برنامجك بوجود الأخطاء ويتعامل معها ثم يكمل تنفيذه بسلام. فالبرنامج الآتي يتسبب بخطأ القسمة على صفر. جربه: def spam(divideBy): return 42 / divideBy print(spam(2)) print(spam(12)) print(spam(0)) print(spam(1)) عرفنا الدالة spam()‎ ومررنا إليها وسيطًا بقيم مختلفة وطبعنا قيمة قسمة العدد 42 على القيمة الممررة. هذا هو ناتج تنفيذ الشيفرة السابقة: 21.0 3.5 Traceback (most recent call last): File "C:/zeroDivide.py", line 6, in <module> print(spam(0)) File "C:/zeroDivide.py", line 2, in spam return 42 / divideBy ZeroDivisionError: division by zero يظهر الاستثناء ZeroDivisionError حينما نقسم عددًا على صفر. ويظهر لنا رقم السطر الذي يسبب هذا الاستثناء، وستعرف منه أن العبارة return في الدالة spam()‎ هي من تسبب الخطأ. يمكن التعامل مع الاستثناءات باستخدام العبارتين try و except. إذ نضع الشيفرة التي قد تسبب خطأ أو استثناءً ضمن قسم العبارة try، وسينتقل تنفيذ البرنامج إلى بداية القسم الذي يلي العبارة except في حال حدوث استثناء. يمكنك وضع الشيفرة التي قد تسبب بخطأ القسمة على الصفر ضمن كتلة try واستخدام كتلة except للتعامل مع حدوث الخطأ: def spam(divideBy): try: return 42 / divideBy except ZeroDivisionError: print('Error: Invalid argument.') print(spam(2)) print(spam(12)) print(spam(0)) print(spam(1)) حينما تتسبب الشيفرة الموجودة ضمن try باستثناء، فسينتقل تنفيذ البرنامج مباشرةً إلى الشيفرة الموجودة في except، وبعد تنفيذ تلك الشيفرة فسيكمل تنفيذ البرنامج بشكل طبيعي. ستكون نتيجة تنفيذ الشيفرة السابقة هي: 21.0 3.5 Error: Invalid argument. None 42.0 لاحظ أن أية أخطاء تحدث أثناء استدعاءات الدوال ضمن كتلة try فستعالج أيضًا. جرب البرنامج الآتي التي يستدعي الدالة spam()‎ ضمن كتلة try: def spam(divideBy): return 42 / divideBy try: print(spam(2)) print(spam(12)) print(spam(0)) print(spam(1)) except ZeroDivisionError: print('Error: Invalid argument.') سيكون الناتج كما يلي: 21.0 3.5 Error: Invalid argument. سبب عدم تنفيذ print(spam(1))‎ هو انتقال التنفيذ إلى الشيفرة الموجودة في كتلة except مباشرةً، ولن تعود لإكمال بقية كتلة try، بل ستكمل تنفيذ بقية البرنامج كالمعتاد. برنامج قصير لرسم زكزاك لنستعمل المفاهيم البرمجية التي تعلمناها حتى الآن لإنشاء برنامج حركي بسيط. سينشِئ هذا البرنامج شكل زكزاك إلى أن يوقفه المستخدم بالضغط على زر Stop في محرر Mu أو بالضغط على Ctrl+C. سيبدو ناتج البرنامج بعد تنفيذه كما يلي: ******** ******** ******** ******** ******** ******** ******** ******** ******** اكتب الشيفرة الآتية واحفظها في ملف باسم zigzag.py: import time, sys indent = 0 # كم فراغًا نضع كمسافة بادئة indentIncreasing = True # هل ستزيد المسافة البادئة أم لا try: while True: # حلقة تكرار البرنامج الأساسية print(' ' * indent, end='') print('********') time.sleep(0.1) # توقف لعُشر ثانية if indentIncreasing: # زيادة المسافة البادئة indent = indent + 1 if indent == 20: # تغيير الاتجاه indentIncreasing = False else: # إنقاص عدد الفراغات indent = indent - 1 if indent == 0: # تغيير الاتجاه indentIncreasing = True except KeyboardInterrupt: sys.exit() لننظر إلى الشيفرة سطرًا بسطر بدءًا من الأعلى. import time, sys indent = 0 # كم فراغًا نضع كمسافة بادئة indentIncreasing = True # هل ستزيد المسافة البادئة أم لا في البداية علينا أن نستورد الوحدتين time و sys، وسيستخدم برنامجنا متغيرين اثنين: المتغير indent الذي يتتبع كم فراغًا يجب أن نضع كمسافة بادئة قبل النجوم الثمانية، و indentIncreasing الذي يحتوي على قيمة منطقية بوليانية لتحدد إذا كانت المسافة البادئة ستزيد أم تنقص. try: while True: # حلقة تكرار البرنامج الأساسية print(' ' * indent, end='') print('********') time.sleep(0.1) # توقف لعُشر ثانية ثم وضعنا بقية البرنامج داخل عبارة try؛ فحينما يضغط المستخدم على Ctrl+C أثناء تشغيل برنامج بايثون فستطلق الاستثناء KeyboardInterrupt، وإذا لم تكن هنالك عبارة try-except لمعالجة الاستثناء فسينهار البرنامج وتظهر رسالة خطأ قبيحة. لتفادي ذلك سنعالج الاستثناء KeyboardInterrupt بأنفسنا باستدعاء الدالة sys.exit()‎ (هذه الشيفرة موجودة بعد نهاية كتلة try). حلقة التكرار اللانهائية while True:‎ ستكرر التعليمات الموجودة في برنامجنا للأبد، واستخدمنا التعبير ‎' ' * indent‎ لطباعة العدد الصحيح من المسافات البادئة، لكننا لا نريد أن ننتقل إلى سطر جديد بعد تلك الفراغات فنمرر الوسيط end=''‎ إلى الدالة print()‎. الاستدعاء الثاني للدالة print()‎ سيطبع لنا 8 نجوم. لم نشرح الدالة time.sleep()‎ بعد، لكن يكفي القول أنها توقف تشغيل البرنامج مؤقتًا لعُشر ثانية 0.1. if indentIncreasing: # زيادة المسافة البادئة indent = indent + 1 if indent == 20: # تغيير الاتجاه indentIncreasing = False ثم سنعدل مقدار المسافات البادئة للمرة القادمة التي تنفذ فيها حلقة while. فإذا كان indentIncreasing هو True فسنضيف واحد إلى indent. لكن حينما تصل المسافة البادئة إلى 20 فنرغب بتقليل المسافة البادئة: else: # إنقاص عدد الفراغات indent = indent - 1 if indent == 0: # تغيير الاتجاه indentIncreasing = True أما إذا كانت indentIncreasing هي False فسننقص واحد من المتغير indent، وحينما تصل قيمته إلى 0 فسنحتاج إلى زيادة المسافات البادئة مجددًا، وفي كلتا الحالتين سيعود تنفيذ البرنامج إلى بداية حلقة التكرار لطباعة النجوم مجددًا. except KeyboardInterrupt: sys.exit() إذا ضغط المستخدم على Ctrl+C في أي مرحلة من مراحل تنفيذ حلقة التكرار الموجودة داخل كتلة try فسيطلق الاستثناء KeyboardInterrrupt ثم يعالج في عبارة except، سيستمر تنفيذ البرنامج داخل كتلة expect الذي سيشغل الدالة sys.exit()‎ لإنهاء البرنامج. وعلى الرغم من أن حلقة التكرار لانهائية، لكننا نوفر طريقة آمنة لإنهاء تشغيل التطبيق من المستخدم. الخلاصة الداوال هي طريقة أساسية لتجزئة شيفراتك إلى مجموعات، ولأن المتغيرات داخل الدوال تكون في مجال محلي خاص بها فلا تؤثر الشيفرات الموجودة في إحدى الدوال على الأخرى، وبالتالي تقل الشيفرات المسؤولة عن تغيير قيمة أحد المتغيرات وبالتالي تسهل عملية تنقيح البرنامج ومعرفة الأخطاء. الدوال هي أداة رائعة لتنظيم شيفراتك، ويمكنك أن تفكر فيها على أنها صناديق سوداء: فهي تقبل المدخلات على شكل معاملات وتخرج النتائج على شكل قيم معادة، والشيفرات داخلها لا تؤثر على بقية الدوال. تعلمنا استخدام العبارتين try و except التي تتولى معالجة الاستثناءات، فكان حدوث أي خطأ في البرنامج سيؤدي إلى انهياره كما رأينا في المقالات السابقة، لكن بتعلمنا لمعالجة الاستثناءات أصبح بإمكاننا بناء تطبيقات تتعامل مع الأخطاء الشائعة دون مشاكل. والآن بعد أن تعلمت الدوال في هذا المقال، ما رأيك أن تجرب ما تعلمته في التطبيق العملي الموالي؟ وطبعًا، لا تنسَ مشاركتنا نتائج تطبيقك في التعليقات: اكتب دالةً باسم collatz()‎ التي تقبل معاملًا واحدًا اسمه number، إذا كان number زوجيًا فستطبع الدالة number // 2 ثم تعيد تلك القيمة، وإذا كان العدد number فرديًا فستطبع وتعيد ناتج 3 * number + 1. ثم اكتب برنامجًا يسمح للمستخدم بإدخال رقم صحيح واستمر باستدعاء الدالة collatz()‎ على ذاك الرقم حتى تعيد الدالة القيمة 1. (ستعمل هذه الدالة لجميع الأعداد الصحيحة، وستكون النتيجة دومًا 1! لا يعرف علماء الرياضيات تحديدًا لماذا، لكن برنامجك هو تطبيق عملي على معضلة كولاتز، حتى أن بعضهم يطلق عليها «أبسط معضلة رياضية مستحيلة»). تذكر أن تحول القيمة المعادة من input()‎ إلى رقم صحيح عبر الدالة int()‎، وإلا فستعدها بايثون على أنها سلسلة نصية. تلميحة: يكون العدد number زوجيًا إذا كان باقي القسمة على 2 هو 0 أي number % 2 == 0 وفرديًا إذا كان number % 2 == 1. يجب أن يبدو شكل تنفيذ البرنامج كما يلي: Enter number: 3 10 5 16 8 4 2 1 ترجمة -وبتصرف- للفصل Functions من كتاب Automate the boring stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: بنى التحكم في لغة بايثون Python أنواع البيانات والعمليات الأساسية في لغة بايثون تعلم لغة بايثون النسخة العربية الكاملة لكتاب البرمجة بلغة بايثون
  6. أصبحت تعرف أن هنالك تعليمات وأن البرامج تتألف من سلسلة من التعليمات، لكن قوة البرمجة الحقيقية لا تقع في تنفيذ مجموعة من التعليمات واحدةً تلو الأخرى وكأنك تتبضع قائمة التسوق. فاعتمادًا على نتيجة بعض التعبيرات البرمجية يستطيع البرنامج أن يتخطى تنفيذ مجموعة من التعليمات أو تكررها أو يختار بعضها لتنفيذه. من النادر أن تجد برامج تنفذ من أول سطر حتى آخر سطر بالترتيب، لذا تكون هنالك حاجة إلى بنى التحكم flow control لتقرير ما هي التعليمات البرمجية التي ستنفذ ووفق أي شروط. يمكن بسهولة تحويل بنى التحكم البرمجية إلى رموز في مخططات التدفق flowcharts التي نرسمها، لذا سأوفر لك نسخة منها تحاكي الشيفرات التي نكبها. الشكل الآتي يظهر مخططًا تدفقيًا يساعد في اتخاذ القرار عمّا سنفعله إذا كان الجو ماطرًا. تتبع الأسهم من البداية إلى النهاية: مخطط تدفقي يخبرك ما تفعل إن كانت السماء تمطر من الشائع أن يكون في المخططات التدفقية أكثر من طريق من البداية إلى النهاية، والأمر سيان بالنسبة إلى شيفرات تطبيقات الحاسوب. تُمثَّل نقاط التفرع في المخططات التدفقية باستخدام شكل المعيّن diamond بينما تُمثَّل بقية الخطوات بمستطيلات عادية، وتكون البداية والنهاية على شكل مستطيل بحواف مدورة. قبل أن تتعلم عن عبارات بنى التحكم، فعليك أن تعرف كيفية تمثيل خيارات «نعم» و «لا»، بعدها ستحتاج إلى فهم كيفية كتابة نقاط التفرع بلغة بايثون؛ ولهذا الغرض سنحتاج إلى تعلم القيم المنطقية أو البوليانية Boolean وعوامل المقارنة والعوامل المنطقية. القيم المنطقية Boolean يكون لأنواع البيانات التي تعلمناها سابقًا من سلاسل نصية وأرقام صحيحة وأرقام ذات فاصلة، عدد لا متناهي من القيم التي يمكن أن تأخذها. لكن للقيم المنطقية أو البوليانية تملك نوعين من القيم فقط: True و False (نقول عنها أنها قيم بوليانية Boolean نسبةً إلى العالم الرياضي جورج بولي)، وحين إدخالها في شيفرة بايثون فستلاحظ عدم وجود علامات الاقتباس التي تحيط بالسلاسل النصية، وتبدأ دومًا بالحرف الكبير T أو F وتكون بقية الكلمة بأحرف صغيرة. أدخل ما يلي في الصدفة التفاعلية، لاحظ الأخطاء التي ستظهر لك في بعض التعليمات: ➊ >>> spam = True >>> spam True ➋ >>> true Traceback (most recent call last): File "<pyshell#2>", line 1, in <module> true NameError: name 'true' is not defined ➌ >>> True = 2 + 2 SyntaxError: can't assign to keyword وكأي قيمة أخرى، يمكن للقيم المنطقية أن تستعمل في التعابير البرمجية ويمكن أن تخزن في المتغيرات ➊، وإن لم تستعمل حالة الأحرف الصحيحة ➋ أو جربت استخدام True أو False لأسماء المتغيرات ➌ فستظهر لك رسالة خطأ. عوامل المقارنة تقارن عوامل المقارنة comparison operators بين قيمتين وتكون النتيجة هي قيمة منطقية بوليانية واحدة، الجدول الآتي يستعرض عوامل المقارنة: العامل المعنى == يساوي !=‎ لا يساوي < أصغر > أكبر <= أصغر أو يساوي >=‎ أكبر أو يساوي الجدول 2: عوامل المقارنة تكون نتيجة استخدام هذه العوامل هي True أو False اعتمادًا على القيم التي تعطيها لها. لنجرب الآن بعض تلك العوامل ولنبدأ بالعاملين == و ‎!=‎: >>> 42 == 42 True >>> 42 == 99 False >>> 2 != 3 True >>> 2 != 2 False وكما قد تتوقع، ستكون نتيجة عامل «يساوي» == هي True حينما تساوت القيمتان على يمينه ويساره، وعامل «لا يساوي» ستكون نتيجته True حينما تختلف القيمتان على يمينه ويساره. يمكننا استخدام العاملين == و ‎!=‎ على أي نوع من أنواع البيانات: >>> 'hello' == 'hello' True >>> 'hello' == 'Hello' False >>> 'dog' != 'cat' True >>> True == True True >>> True != False True >>> 42 == 42.0 True ➊ >>> 42 == '42' False لاحظ أن القيم العددية سواءً كانت صحيحة int أو ذات فاصلة عائمة float لا تتساوي مع القيمة النصية. فالتعبير 42 == '42' ➊ نتيجته هي Flase لأن بايثون تعدّ الرقم 42 مختلفًا عن السلسلة النصية '42'. أما المعاملات ‎>‎ و ‎<‎ و ‎<=‎ و ‎>=‎ في لا تعمل إلا مع القيم العددية: >>> 42 < 100 True >>> 42 > 100 False >>> 42 < 42 False >>> eggCount = 42 ➊ >>> eggCount <= 42 True >>> myAge = 29 ➋ >>> myAge >= 10 True الفرق بين عامل = و == قد تلاحظ أن عامل المساواة == يحتوي على إشارتي يساوي، بينما عامل الإسناد فيه إشارة يساوي واحدة. ومن السهل الخلط بينهما بالنسبة إلى المبتدئين، لذا تذكر أن: عامل المساواة == يتأكد إن كانت القيمتان عن يمينه ويساره متساويتين. عامل الإسناد = يضع التي على اليمين في المتغير الذي على اليسار. قد يساعدك في التذكر أن عامل المساواة == فيه حرفان، مثل معامل عدم المساواة ‎!=‎ تمامًا. ستستخدم عوامل المقارنة كثيرًا لمقارنة قيمة أحد المتغيرات مع قيمة أخرى، كما في eggCount <= 42 ➊ و myAge >= 10 ➋، فلو كنتَ تعرف قيمة المتغير كيف ستكون قبل أن تشغل برنامجك فلا حاجة إلى المقارنة كلها، فليس من المنطقي أن تكتب 'dog' != 'cat' في شيفرتك بل تكتب True مباشرةً. سترى أمثلة كثيرة عن ذلك أثناء تعلمك لبنى التحكم. العوامل المنطقية البوليانية تستخدم العوامل المنطقية الثلاثة and و or و not لمقارنة القيم المنطقية، وكما في عوامل المقارنة ستكون نتيجة هذه التعابير هي قيمة منطقية، ولنبدأ شرح هذه العوامل بالتفصيل بدءًا من العامل and و or. العوامل المنطقية الثنائية يعمل العاملان and و or على قيمتين أو تعبيرين منطقيين، لهذا يسميان بالعوامل المنطقية الثنائية binary Boolean operators. ينتج العامل nad القيمة True إذا كانت كلا القيمتان المنطقيتان تساوي True، وإلا فالنتيجة هي False. أدخل التعابير البرمجية الآتية في الصدفة التفاعلية لترى أثر هذا العامل عمليًا: >>> True and True True >>> True and False False جداول الحقيقة truth table هو جدول في الجبر المنطقي البولياني يوضح ناتج كل شكل من أشكال التعابير المنطقية. جدول الحقيقة الآتي يوضح ناتج كل عملية ممكنة مع العامل and: التعبير النتيجة True and True True True and False False False and True False False and False False الجدول 2: جدول الحقيقة للعامل and وفي المقابل تكون نتيجة العامل or هي True إذا كان أحد القيمتين المنطقيتين يساوي True، أما إذا كانت كلتاهما Flase فنتيجة التعبير هي False: >>> False or True True >>> False or False False يمكنك أن تعرف ناتج كل تعبير ممكن مع العامل or من جدول الحقيقة الخاص به. التعبير النتيجة True or True True True or False True False or True True False or False False الجدول 3: جدول الحقيقة للعامل or العامل not وعلى النقيض من العاملين and و or، يستخدم العامل not على قيمة أو تعبير منطقي واحد، ولهذا هو عامل أحادي unary operator. ما يفعله العامل not هو عكس القيمة المنطقية الحالية: >>> not True False ➊ >>> not not not not True True وكما في نفي النفي في حديثنا العادي، يمكننا أن نجعل العامل not متشعبًا ➊، لكن لا يوجد سبب لفعل ذلك في البرامج العملية. الجدول الآتي هو جدول الحقيقة للعامل not: التعبير النتيجة not True False not False True الجدول 4: جدول الحقيقة للعامل not المزج بين العوامل المنطقية وعوامل المقارنة لما كانت نتيجة استخدام عوامل المقارنة هي قيمة منطقية، فيمكننا استخدامها في تعابير برمجية مع العوامل المنطقية. تذكر أن العوامل المنطقية and و or و not هي عوامل منطقية لأنها تعمل على القيم المنطقية True و False؛ بينما التعابير التي تحتوي على عوامل مقارنة مثل ‎4 < 5 ليست قيمًا منطقية بحد ذاتها لكنها تعابير تُنتِج قيمًا منطقية. جرب إدخال بعض التعابير المنطقية التي فيها عوامل مقارنة في الصدفة التفاعلية: >>> (4 < 5) and (5 < 6) True >>> (4 < 5) and (9 < 6) False >>> (1 == 2) or (2 == 2) True سيقدر الحاسوب قيمة التعبير على اليسار أولًا، ثم قيمة التعبير الذي على اليمين، ثم بعد أن يعرف ما هي القيمة المنطقية لكلٍ منها فسيقدر قيمة التعبير البرمجي كاملًا ويخرج قيمة منطقية وحيدة. يمكنك أن تتخيل أن عملية تقدير قيمة التعبير البرمجي ‎(4 < 5) and (5 < 6)‎ تشبه ما يلي: يمكنك أن تستعمل أكثر من عامل منطقي في تعبير واحد، بالإضافة إلى عوامل المقارنة: >>> 2 + 2 == 4 and not 2 + 2 == 5 and 2 * 2 == 2 + 2 True سيكون للعوامل المنطقية ترتيب لتقدير القيمة كما في العوامل الرياضية، فبعد تقدير قيمة العمليات الحسابية وعمليات المقارنة، ستعمل بايثون على عوامل not أولًا، ثم عوامل and ثم عوامل or. عناصر بنى التحكم تبدأ عبارات بنى التحكم بجزء يسمى عادةً بالشرط condition ويكون متبوعًا دومًا بكتلة برمجية اشتراطية clause، وقبل أن نتعلم عن بنى التحكم في بايثون فسنشرح ما هو الشرط وما هي الكتلة البرمجية. الشروط تعدّ جميع التعابير المنطقية البوليانية التي رأيتها حتى الآن شروطًا conditions، فالشروط هي تعابير برمجية، لكنها تطلق في سياق الحديث عن بنى التحكم. يكون للشروط دومًا نتيجة منطقية إما True أو False، وتقرر بنى التحكم ما الذي يجب فعله اعتمادًا على الشرط إن كان True أو False، وجميع بنى التحكم تقريبًا تستخدم الشروط. الكتل البرمجة تجمَِع أسطر الشيفرات البرمجية في بايثون على شكل كتل blocks، ويمكنك أن تعرف متى تبدأ الكتلة ومتى تنتهي من المسافة البادئة indent للأسطر البرمجية. هنالك ثلاث قواعد للكتل البرمجية في بايثون: تبدأ الكتلة البرمجية حين زيادة المسافة البادئة. يمكن أن تحتوي الكتل البرمجية على كتل أخرى. تنتهي الكتل حينما تقل المسافة البادئة إلى الصفر أو إلى المسافة البادئة للكتلة الأب. من الأسهل فهم الكتل البرمجية بالنظر إلى بعض الشيفرات التي لها مسافة بادئة، لذا لنعثر على الكتل البرمجية في البرنامج البسيط الآتي: name = ‘Ahmed’ password = 'swordfish' if name == 'Ahmed': ➊ print('Hello, Ahmed’) if password == 'swordfish': ➋ print('Access granted.') else: ➌ print('Wrong password.') تبدأ أول كتلة من الشيفرات ➊ في السطر print('Hello, Ahmed’)‎ وتضم إليها جميع الأسطر البرمجية التي تليها، وداخل هذه الكتلة هنالك كتلة أخرى ➋ التي تحتوي سطرًا واحدًا داخلها فيه print('Access granted.')‎، أما الكتلة الثالثة ➌ والأخيرة ففيها print('Wrong password.')‎. تنفيذ البرنامج تبدأ بايثون بتنفيذ مثالنا السابق hello.py من أول البرنامج حتى نهايته سطرًا بسطر، وعملية تنفيذ البرنامج (التي تسمى program execution أو اختصارًا execution) هي اصطلاح يشير إلى التعليمة البرمجية التي يجري تنفيذها حاليًا، فلو كانت شيفرة برنامجك مطبوعةً على ورقة وتشير بإصبعك إلى السطر الذي يجري تنفيذه حاليًا فتشير إصبعك هنا إلى خط سير تنفيذ البرنامج. لا تنفذ جميع البرامج من الأعلى إلى الأسفل، فلو كنت تستعمل إصبعك لتتبع خط سير أحد البرامج التي فيها بنى تحكم فسترى أنك تنتقل من مكانٍ إلى آخر في الشيفرة المصدرية، وأنك قد تتخطى أجزاء من الشيفرة كليًا. عبارات بنى التحكم حان الوقت الآن لنتحدث عن أهم جزء من بنى التحكم: عبارات بنى التحكم نفسها. تمثل العبارات في مخططات التدفق -مثل التي رأيتها في صورة "مخطط تدفقي يخبرك ما تفعل إن كانت السماء تمطر" على شكل معيّن، وتشير إلى القرارات التي يتخذها برنامجك. عبارة if أكثر نوع شائع من بنى التحكم هو العبارة الشرطية if، ففيها ستُنفَّذ الكتلة البرمجية التي تلي if إذا كان الشرط محققًا أي True، وسيتخطاها البرنامج إن لم يكن الشرط محققًا أي False. فإذا أردنا أن نقرأ عبارة if البرمجة باللغة العربية فسنقول «إذا كان الشرط محققًا، فنفذ الكتلة البرمجية الآتية». تتألف عبارة if في بايثون مما يلي: الكلمة المفتاحية if الشرط، وهو التعبير الذي تكون نتيجته هي True أو False نقطتان رأسيتان : كتلة برمجية تلي ما سبق، تكون فيها الأسطر مسبوقة بمسافة بادئة لنقل مثلًا أنك تريد كتابة شيفرة تتحقق إن كان اسم المستخدم هو Ahmed، وعلى فرض أننا قد أسندنا سابقًا قيمةً ما إلى المتغير name: if name == 'Ahmed': print('Hi, Ahmed.') تنتهي جميع عبارات بنى التحكم بنقطتين رأسيتين : متبوعة بكتلة برمجية، والكتلة البرمجية في مثالنا هي التي تحتوي على print('Hi, Ahmed.')‎. يوضح الشكل الآتي المخطط التدفقي للشيفرة السابقة: عبارات else يمكن اختياريًا أن يأتي بعد كتلة if العبارة else، وتنفذ كتلة else في حال كان شرط عبارة if غير محقق False. أي بالعربية يمكننا أن نقول «إذا كان الشرط محققًا، فنفذ الكتلة البرمجية الآتية، وإلا فنفذ هذه الكتلة». لا تملك عبارة else شرطًا، وتتألف else مما يلي: الكلمة المفتاحية else نقطتان رأسيتان : كتلة برمجية تلي ما سبق، تكون فيها الأسطر مسبوقة بمسافة بادئة وبالعودة إلى مثالنا السابق للترحيب بالمستخدم، فسنضيف العبارة else لطباعة تحية مختلفة لأي شخص ليس اسمه أحمد: if name == 'Ahmed': print('Hi, Ahmed.') else: print('Hello, stranger.') يبين المخطط التدفقي للبرنامج السابق عبارات elif تعرفنا سابقًا على عبارة if و else التي يجب أن تنفذ إحداهما، لكن ماذا لو كنّا نريد وجود أكثر من احتمال أو أكثر من شرط؟ تعمل العبارة elif كأنها «وإلا إذا كان كذا» else if، وتأتي بعد عبارة if أو elif أخرى. توفر عبارة elif شرطًا بديلًا يمكن التحقق إن كان محققًا إن كانت الشروط التي تسبقه غير محققة. تتألف عبارة elif في بايثون مما يلي: الكلمة المفتاحية elif الشرط، وهو التعبير الذي تكون نتيجته هي True أو False نقطتان رأسيتان : كتلة برمجية تلي ما سبق، تكون فيها الأسطر مسبوقة بمسافة بادئة لنضف عبارة elif إلى برنامجنا الذي نتحقق فيه من اسم المستخدم: if name == 'Ahmed': print('Hi, Ahmed.') elif age < 12: print('You are not Ahmed, kiddo.') سنتحقق هذه المرة من عمر المستخدم، فإن كان عمره أقل من 12 فسيقول له «أنت لست أحمد يا غلام!». يمكننا تمثيل البرنامج بمخطط التدفق الآتي: مخطط التدفق للعبارة elif ستفَّذ الكتلة البرمجية التي تلي elif إن كان عمر المستخدم age < 12 وكان الشرط name == 'Ahmed'‎ غير محقق False. لكن إن كان كلا الشرطين غير محقق فسيتجاوز البرنامج تنفيذ الكتلتين البرمجيتين، وليس من الضروري أي ينفذ أحد الكتل البرمجية، فقد تنفذ عبارة واحدة أو لا تنفذ أي عبارة. بعد أن يتحقق شرط إحدى العبارات الشرطية فسيتجاوز البرنامج بقية عبارات elif كلها. فعلى سبيل المثال أنشِئ الملف vampire.py وضع فيه الشيفرة الآتية: age = 3000 if name == Ahmed: print('Hi, Ahmed.') elif age < 12: print('You are not Ahmed, kiddo.') elif age > 2000: print('Unlike you, Ahmed is not an vampire.') elif age > 100: print('You are not Ahmed, grannie.') أضفنا هنا عبارتَي elif لبرنامج الترحيب بالمستخدم، وأضفنا جوابين مختلفين بناءً على العمر age. يظهر المخطط التدفقي الآتي سير عمل البرنامج: المخطط التدفقي لعبارات elif متعددة في برنامج vampire.py. لترتيب عبارات elif أهمية، ولتجربة أهمية ترتيبها فلنحاول إضافة علّة لبرنامجنا. تذكر أن البرنامج سيتخطى عبارات elif بعد تحقيق شرط إحداها، لذا إذا غيرنا ترتيب الشيفرة إلى ما يلي وحفظناه باسم vampire2.py: name = 'Abdullatif' age = 3000 if name == 'Ahmed': print('Hi, Ahmed.') elif age < 12: print('You are not Ahmed, kiddo.') ➊ elif age > 100: print('You are not Ahmed, grannie.') elif age > 2000: print('Unlike you, Ahmed is not an undead, vampire.') إذا جربنا البرنامج وكانت قيمة المتغير age تساوي 3000 مثلًا، فقد تتوقع أن برنامج سيطبع العبارة التي تقول أن عمره أكثر من 2000 سنة، لكن ولمّا كانت الشرط age > 100 محققًا True (وذلك لأن 3000 أكبر من 100 بالفعل) ➊، فستعرض عبارة الترحيب بالعجوز وستخطى تنفيذ البرنامج جميع عبارات elif الأخرى. لذا من المهم الانتباه إلى ترتيب عبارات elif. المخطط التدفقي الآتي يظهر سير تنفيذ الشيفرة السابقة. لاحظ كيف جرى تبديل المعين الذي يحتوي على age > 100 و age > 2000. المخطط التدفقي لتنفيذ برنامج vampire2.py، لاحظ أن الفرع الذي عليه إشارة × لا ينفذ أبدًا، لكنه إذا كان العمر age أكبر من 2000 فهو بكل تأكيد أكبر من 100 أيضًا. يمكنك أن تضع عبارة else بعد آخر عبارة elif اختياريًا، وفي هذه الحالة سنضمن تنفيذ كتلة برمجية واحدة فقط لا غير، أي في حال كانت جميع شروط if و elif هي False فستنفَّذ كتلة else. لنعد كتابة مثال الترحيب بالمستخدم لنستعمل فيه if و elif و else: name = 'Abdullatif' age = 3000 if name == 'Ahmed': print('Hi, Ahmed.') elif age < 12: print('You are not Ahmed, kiddo.') else: print('You are neither Ahmed nor a little kid.') يظهر الشكل الآتي المخطط التدفقي للمثال السابق، الذي سنسميه littleKid.py. المخطط التدفقي لبرنامج liitleKid.py. يمكننا وصف هذا النمط من بنى التحكم باللغة العربية: «إذا كان أول شرط محققًا فافعل كذا، وإلا إن كان الشرط الثاني محققًا فافعل كذا، وإلا فافعل كذا». من المهم أن تنتبه إلى ترتيب عبارات if وelif و else حين استخدامها لكي تتجنب العلل المنطقية في برامجك. وتذكر أن هنالك عبارة if وحيدة فقط لا غير، وأي عبارات elif يجب أن تأتي بعدها؛ وإذا أردت ضمان تنفيذ إحدى الكتل البرمجية فأنهِ بنى التحكم بعبارة else. حلقة التكرار while يمكنك أن تعيد تنفيذ إحدى الكتل البرمجية مرارًا وتكرارًا باستخدام عبارة while. وستُنفَّذ الشيفرة الموجودة في الكتلة التي تلي شرط while طالما كان الشرط محققًا True. تتألف عبارة while في بايثون مما يلي: الكلمة المفتاحية while الشرط، وهو التعبير الذي تكون نتيجته هي True أو False نقطتان رأسيتان : كتلة برمجية تلي ما سبق، تكون فيها الأسطر مسبوقة بمسافة بادئة يمكنك ملاحظة أن عبارة while شبيهة جدًا بعبارة if، والفرق بينهما هو في السلوك. فبعد الانتهاء من الكتلة التي تلي شرط if سيكمل البرنامج تنفيذ الشيفرة التي تليها، لكن في نهاية كتلة while فسيعود تنفيذ البرنامج إلى بداية عبارة while. نسمي عبارة while عادةً «حلقة التكرار while» أو «حلقة while». للنظر إلى الفرق بين العبارة الشرطية if وحلقة while لهما نفس الشرط وتفعلان نفس الفعل في الكتلة التي تليهما. هذه هي الشيفرة التي نستعمل العبارة الشرطية if فيها: spam = 0 if spam < 5: print('Hello, world.') spam = spam + 1 وهذه هي الشيفرة التي نستعمل الحلقة while فيها: spam = 0 while spam < 5: print('Hello, world.') spam = spam + 1 تشبه هذه التعابير بعضها بعضًا، إذ تتحقق if و while من أن قيمة المتغير spam أصغر من 5، ثم تعرض رسالةً ترحيبيةً وتزيد قيمة المتغير spam بمقدار 1. لكن حينما تشغل البرنامجين السابقين فستجد نتيجةً مختلفةً لكلٍ منهما، ففي البرنامج الذي فيه if سترى عبارة الترحيب مرة واحدة، بينما تكرر العبارة الترحيبية 5 مرات في برنامج while! لننظر إلى المخططين التدفقيين للبرنامجين السابقين في الشكلين المواليين لنفهم ما حدث: المخطط التدفقي للبرنامج الذي يحتوي على العبارة الشرطية if. المخطط التدفقي للبرنامج الذي يحتوي على العبارة الشرطية while. تتحقق العبارة الشرطية if من الشرط وتطبع "Hello, World" مرةً واحدةً بعد تحقق الشرط، ثم ينتهي تنفيذ البرنامج. بينما البرنامج الذي فيه حلقة while فسينفذ 5 مرات لأن قيمة العدد spam تزيد مرة في نهاية كل حلقة تكرار، وهذا يعني أن الحلقة ستنفذ 5 مرات قبل أن يصبح الشرط spam < 5 غير محقق False. ستجري عملية التحقق من شرط حلقة while في بداية كل دورة iteration، أي في بداية كل تنفيذ لحلقة while؛ وإذا كان الشرط محققًا True فستنفذ الكتلة، ثم بعد ذلك يعاد التحقق من الشرط مجددًا. لاحظ أنه إذا كان شرط حلقة while غير محقق False من أول مرة فلن تنفَّذ الحلقة أبدًا وسيتخطاها البرنامج. حلقة while المزعجة هذا برنامج بسيط يطلب منك باستمرار أن تدخل "your name" في سطر الأوامر حين سؤاله عن اسمك. أنشِئ ملفًا جديدًا من File ثم New وأدخل الشيفرة الآتية واحفظها في الملف yourName.py: ➊ name = '' ➋ while name != 'your name': print('Please type your name.') ➌ name = input() ➍ print('Thank you!') يضبط البرنامجُ المتغيرَ name إلى سلسلة نصية فارغة ➊، وبهذا يكون الشرط name != 'your name'‎ محققًا True وبالتالي سيبدأ تنفيذ حلقة while ➋. سيسأل البرنامجُ المستخدمَ عن اسمه، ثم يأخذ المدخلات ويخزنها في المتغير name ➌، ولمّا كنّا قد وصلنا إلى نهاية حلقة التكرار فسنعود إلى بداية حلقة while ونتحقق من الشرط، وإن لم يكن الاسم name مساويًا إلى السلسلة النصية 'your name' فيسكون الشرط محققًا True وسيعاد تنفيذ حلقة while مجددًا. لكن حينما يدخل المستخدم الكلمتين your name فسيصبح شرط حلقة while غير محقق False، وبالتالي بدلًا من إعادة تكرار الحلقة فسيكمل برنامجنا تنفيذ بقية البرنامج ➍. المخطط التدفقي في الشكل الآتي يوضح آلية عمل البرنامج yourName.py: المخطط التدفقي للبرنامج yourName.py. لنجرب الآن البرنامج، بالضغط على زر F5 لتشغيله، ثم كتابة أي عبارة سوى "your name" عدة مرات قبل أن نرضخ للضغط الذي يمارسه البرنامج تجاهنا ونكتب "your name". Please type your name. Abdullatif Please type your name. Hsoub Please type your name. %#@#%*(^&!!! Please type your name. your name Thank you! إذا لم ندخل "your name" أبدًا فلن يصبح شرط while غير محقق False وبالتالي سيستمر تنفيذ البرنامج إلى الأبد. وفي حالة برنامجنا كانت لدينا الدالة input()‎ التي تمكننا من إكمال سير البرنامج حينما ندخل العبارة الصحيحة وبالتالي تتغير حالة الشرط، لكن قد لا يتغير الشرط في بعض البرامج مما يسبب مشكلة، لذا هنالك حاجة لتعلم طريقة للخروج من حلقة while. العبارة break هنالك طريقة نجعل فيها برنامجنا يخرج من حلقة while قبل انتهاء تنفيذها. فإذا وصل التنفيذ إلى عبارة break فسيخرج البرنامج من حلقة while مباشرةً. وتتألف عبارة break في بايثون من الكلمة المحجوزة break فقط. أليس ذلك بسيطًا؟ لنكتب برنامجًا يشبه البرنامج السابق لكنه يستخدم العبارة break للخروج من حلقة التكرار، أدخِل الشيفرة الآتية واحفظها في ملف باسم yourName2.py: ➊ while True: print('Please type your name.') ➋ name = input() ➌ if name == 'your name': ➍ break ➎ print('Thank you!') ننشِئ في أول سطر ➊ حلقة تكرار لا نهائية، وذلك بجعل شرط حلقة while محققًا دومًا True، وبالتأكيد إذا وضعنا True شرطًا لحلقة while فستكون قيمته هي True دومًا. بعد أن يبدأ تنفيذ حلقة التكرار فلن يخرج البرنامج منها إلا إذا استخدام عبارة break، لاحظ أن حلقات التكرار اللانهائية التي لا ينتهي تنفيذها أبدًا هي علّة منطقية في البرامج. وكما في المثال السابق، سيطلب البرنامج من المستخدم أن يدخل your name ➋، وسنتحقق إن كان المتغير name يساوي 'your name' باستخدام البنية الشرطية if ➌، وإذا كان الشرط محققًا True فستنفذ العبارة break ➍، وسينتقل التنفيذ إلى خارج الحلقة وستطبع رسالة الشكر ➎. إذا لم يكن شرط العبارة if محققًا فهذا يؤدي إلى دورة جديدة لحلقة while، وسيتحقق البرنامج من شرط تنفيذ حلقة while ➊، ولمّا كان الشرط محققًا True دومًا، فستنفذ حلقة التكرار مجددًا وتسأل المستخدم أن يدخل your name. المخطط التدفقي في الشكل التالي يوضح آلية عمل البرنامج yourName2.py: المخطط التدفقي للبرنامج yourName2.py مع حلقة تكرار لا نهائية، لاحظ أن المسار × لا ينفذ منطقيًا أبدًا لأن شرط الحلقة هو True دومًا. هل وقعت في حلقة تكرار لا نهائية؟ إذا شغلت تطبيقًا يحتوي على علة تؤدي إلى حلقة تكرار لا نهائية، فاضغط على Ctrl+C أو أعد تشغيل الصدفة من Shell ثم Restart Shell؛ مما يرسل إشارة KeyboardInterrupt إلى برنامجك تؤدي إلى إيقاف تشغيله مباشرةً. يمكنك التجربة بإنشاء برنامج بسيط باسم infiniteLoop.py: while True: print('Hello, world!') سيطبع البرنامج السابق العبارة Hello, World!‎ إلى اللانهاية لأن شرط حلقة while محقق True دومًا. قد تستفيد من استخدام الاختصار Ctrl+C لإنهاء تنفيذ البرامج حتى دون أن تكون عالقًا في حلقة تكرار لا نهائية. عبارة continue وكما في عبارة break، نستعمل العبارة continue داخل حلقات التكرار، وحينما يصل التنفيذ إلى عبارة continue فسينتقل تنفيذ البرنامج إلى بداية حلقة التكرار مباشرةً ويعيد التحقق من شرط الحلقة، أي نفس ما يحدث حين الوصول إلى نهاية دورة حلقة التكرار. لنستخدم continue لكتابة برنامج يسأل عن اسم المستخدم وكلمة المرور، أدخل ما يلي في ملف جديد واحفظه باسم swordfish.py: while True: print('Who are you?') name = input() ➊ if name != 'Abdullatif': ➋ continue print('Hello, Abdullatif. What is the password? (It is a fish.)') ➌ password = input() if password == 'swordfish': ➍ break ➎ print('Access granted.') إذا أدخل المستخدم أي اسم باستثناء Abdullatif ➊ فستنقل العبارة continue ➋ التنفيذ إلى بداية حلقة التكرار، وحين إعادة التحقق من شرط الدخول إلى الحلقة فسيكون محققًا دومًا لأنه True. بعد أن يتجاوز المستخدم الشرط الموجود في if فسنسأله عن كلمة المرور ➌، وإذا أدخل كلمة المرور swordfish فستنفذ عبارة break ➍ وبالتالي نخرج من حلقة التكرار كليًا وستطبع العبارة Access granted ➎، وإذا لم تكن كلمة المرور صحيحةً فسنصل إلى نهاية دورة حلقة التكرار ثم نعود إلى بدايتها ونتحقق من الشرط مجددًا الذي هو True دومًا… المخطط التدفقي في الشكل الآتي يوضح آلية عمل البرنامج swordfish.py: المخطط التدفقي للبرنامج swordfish.py، لاحظ أن المسار × لا ينفذ منطقيًا أبدًا لأن شرط الحلقة هو True دومًا. شغل البرنامج السابق وجرب بعض المدخلات، ولن يسألك البرنامج عن كلمة المرور حتى تدعي أنك Abdullatif، وسيطبع لك رسالة أن الوصول مسموح لك إن أدخلت كلمة المرور الصحيحة: Who are you? I'm fine, thanks. Who are you? Who are you? Abdullatif Hello, Abdullatif. What is the password? (It is a fish.) Mary Who are you? Abdullatif Hello, Abdullatif. What is the password? (It is a fish.) swordfish Access granted. القيم التي تكافئ True والقيم التي تكافئ False ستعدّ الشروط في بايثون بعض القيم في أنواع البيانات المختلفة على أنها مكافئة للقيمة True وأخرى للقيمة False. فلو استخدمنا القيم 0 و 0.0 و '' (سلسلة نصية فارغة) في الشروط فستكافئ False، بينما ستكافئ أي قيمة أخرى True. ألقِ نظرةً هنا: name = '' ➊ while not name: print('Enter your name:') name = input() print('How many guests will you have?') numOfGuests = int(input()) ➋ if numOfGuests: ➌ print('Be sure to have enough room for all your guests.') print('Done') يبدأ البرنامج بتهيئة المتغير name مع قيمة نصية فارغة، وبالتالي سيكون شرط حلقة while محققًا True ➊، وسيتحقق أيضًا إذا أدخل المستخدم سلسلة نصية فارغة أثناء سؤاله عن الاسم name (بالضغط مباشرةً على زر Enter دون كتابة شيء). سيبدأ تنفيذ الحلقة بطلب إدخال الاسم وعدد الضيوف، وإذا كان عدد الضيوف numOfGuests ليس صفرًا 0 ➋ فسيكون الشرط محققًا True وسيطبع البرنامج تذكيرًا للمستخدم ➌. كان بإمكاننا كتابة name != ''‎ بدلًا من not name، و numOfGuests != 0 بدلًا من numOfGuests، لكن استخدام القيم التي تكافئ True أو False في شروط سيجعل مقروئية شيفرتك أفضل. حلقات تكرار for والدالة range()‎ ستعمل حلقة while لطالما كان الشرط محققًا True (ومن هنا أتى اسمها while)، لكن ماذا لو أردنا تنفيذ كتلة من الشيفرات لعدد محدد من المرات؟ يمكننا فعل ذلك عبر حلقة التكرار for والدالة range(). ستبدو عبارة for كالآتي for i in range(5): وستتضمن ما يلي: الكلمة المحجوزة for اسم المتغير الكلمة المحجوزة in استدعاء للدالة range()‎ مع تمرير 3 أعداد صحيحة كحد أقصى إليها نقطتان رأسيتان : كتلة برمجية تلي ما سبق، تكون فيها الأسطر مسبوقة بمسافة بادئة لننشئ برنامجًا ولنسمه fiveTimes.py الذي يساعدنا على معاينة حلقة for عمليًا: print('My name is') for i in range(5): print('Hani Five Times (' + str(i) + ')') سترى أن الكتلة البرمجية لحلقة التكرار for تنفذ 5 مرات، وستكون قيمة المتغير i في أول مرة تعمل فيها الحلقة هو 0، وستطبع print()‎ العبارة Hani Five Times (0)‎ ثم بعد أن تنتهي بايثون من تنفيذ أول دورة في حلقة التكرار فسيعود التنفيذ إلى بداية الحلقة وستزيد قيمة المتغير i بمقدار 1، ولهذا سيؤدي استدعاء الدالة range(5)‎ إلى حدوث 5 تكرارات داخل حلقة for، إذ سيبدأ المتغير i من القيمة 0 ثم 1 ثم 2 ثم 3 ثم 4، وعمومًا ستزداد قيمة المتغير إلى أن تصل إلى الرقم الذي مررناه إلى الدالة range()‎ لكن دون تضمينه في النتائج. الشكل الآتي يوضح المخطط التدفقي لهذا البرنامج: إذا شغلت البرنامج فسترى العبارة Hani Five Times متبوعةً بقيمة المتغير i في دورة حلقة for الحالية: My name is Hani Five Times (0) Hani Five Times (1) Hani Five Times (2) Hani Five Times (3) Hani Five Times (4) ملاحظة: يمكنك استخدام break و continue داخل حلقات for أيضًا، وستؤدي العبارة continue إلى تخطي الدورة الحالية لحلقة التكرار والانتقال إلى الدورة الآتية، كما لو أن تنفيذ البرنامج وصل إلى نهاية دورة حلقة for الحالية ثم عاد إلى بداية الحلقة. يمكنك أن تستعمل العبارتين break و continue داخل حلقات while و for فقط، وإذا حاولت أن تجرب استخدامها في مكانٍ آخر فستعطيك بايثون رسالة خطأ. لنأخذ قصة عالم الرياضيات الشهير كارل فريدريش غاوس (قد تعرفه باسم غاوس أو Gauss)، أراد مدرسه حينما كان صغيرًا أن يعطيه وظيفة صعبة وطلب منه جمع الأرقام من 0 إلى 100، وخطرت ببال الفتى غاوس طريقة ذكية للإجابة عن ذاك السؤال بثوانٍ معدودة، لكن لنكتب الآن برنامج بايثون يحسب لنا الناتج ويستعمل الحلقة for: ➊ total = 0 ➋ for num in range(101): ➌ total = total + num ➍ print(total) يفترض أن يكون الناتج 5,050. نضبط قيمة المتغير total إلى 0 ➊ في بداية البرنامج، ثم نبدأ حلقة for ➋ التي ننفذ فيها total = total + num ➌ مئة مرة، وبعد أن تنتهي دورات التكرار المئة فسنكون قد جمعنا الأعداد الصحيحة من 0 إلى 100 في المتغير total، ثم نطبق قيمة total إلى الشاشة ➍. سيعمل برنامجنا بأجزاء من الثانية ويخبرنا بالناتج النهائي. (اكتشف غاوس حينما أعطاه المدرس هذه الوظيفة حلها بطريقة ذكية: هنالك خمسون زوجًا من الأرقام التي يكون مجموعها 101 مثل 1 + 100 و 2 + 99 و 3 + 98 وهلمَّ جرًا، حتى يصل إلى 50 + 51. ولمّا كان جداء 50 × 101 هو 5,050 فسيكون مجموع جميع الأرقام من 0 إلى 100 هو 5,050. يا له من فتى ذكي!) كتابة حلقة while تكافئ حلقة for يمكنك عمليًا أن تكتب حلقة while لتكافئ في عملها حلقة for، وستجد أن كتابة حلقات for مختصرة أكثر. لنعد كتابة المثال iveTimes.py ليستعمل الحلقة while: print('My name is') i = 0 while i < 5: print('Hani Five Times (' + str(i) + ')') i = i + 1 إذا شغلت البرنامج فمن المفترض أن يكون الناتج مماثلًا تمامًا للمثال fiveTimes.py الذي يستعمل حلقة for. اعلم أنك تستطيع كتابة أمور كثيرة في البرمجة بأكثر من طريقة، لكن عليك اختيار الأداة الأنسب لأداء المهمة التي تريدها. وسائط الدالة range()‎: البداية والنهاية والخطوة يمكن أن تستدعى بعض الدوال مع عدّة وسائط arguments مفصولة بفاصلة، والدالة range()‎ هي إحداها. يسمح لك تمرير وسائط للدالة range()‎ أن تغير سلوكها، فمثلًا يمكنك تحديد الرقم الذي يجب أن تبدأ منه الدالة range()‎: for i in range(12, 16): print(i) يمثِّل أول وسيط من أين يجب أن تبدأ حلقة for، ويمثل الوسيط الثاني أين يجب أن تتوقف (دون تضمين هذا الرقم): 12 13 14 15 يمكننا أيضًا استدعاء الدالة range()‎ مع ثلاثة وسائط، ويكون أول وسيطين هما البداية والنهاية، أما الوسيط الثالث فسيكون «الخطوة» step، الذي يشير إلى مقدار زيادة قيمة المتغير عند كل دورة: for i in range(0, 10, 2): print(i) أي أن استدعاء range(0, 10, 2)‎ سيعد من الصفر إلى الثمانية وبخطوة 2: 0 2 4 6 8 الدالة range()‎ مرنة ويمكنك استخدامها لتوليد أي سلسلة أرقام، فمثلًا يمكنك استخدام رقم سالب كوسيط لقيمة الخطوة مما يؤدي إلى العد عكسيًا تنازليًا: for i in range(5, -1, -1): print(i) ستنتج حلقة for السابقة الناتج الآتي: 5 4 3 2 1 0 نجد أن المجال range(5, -1, -1)‎ مع حلقة التكرار for سيؤدي إلى طباعة 5 أعداد تنازليًا من 5 إلى 0. استيراد الوحدات يمكن لجميع برامج بايثون أن تستدعي مجموعةً من الدوال الأساسية نسميها الدوال المضمنة في اللغة أو «الدوال المضمنة» built-in functions، والتي تتضمن الدوال التي تعرفت عليها سابقًا مثل print()‎ و input()‎ و len()‎. تأتي بايثون أيضًا مع مجموعة من الوحدات الأساسية modules التي نسميها بالمكتبة القياسية standard library. تتألف كل وحدة من عدد برامج بايثون التي فيها مجموعة من الدوال التي يمكنك استخدامها في برامجك. فمثلًا تحتوي الوحدة math على مجموعة من الدوال المتعلقة بالعمليات الرياضية، بينما تضم الوحدة random مجموعة من الدوال التي تجري عمليات على الأرقام المولدة عشوائيًا، وهكذا. قبل أن نستخدم الدوال الموجودة في إحدى تلك الوحدات في برامجنا، يجب علينا أولًا استيراد تلك الوحدة باستخدام العبارة import. وتتألف عبارة import مما يلي: الكلمة المحجوزة import اسم الوحدة التي نريد استيرادها واختياريًا أسماء وحدات أخرى نريد استيرادها على أن نفصل بينها بفاصلة بعد أن تستورد إحدى الوحدات فيمكنك أن تستعمل جميع الدوال الرائعة الموجودة فيها، ولنضرب مثالًا الوحدة random التي تمنحنا وصولًا إلى الدالة random.randint()‎ حين استيرادها. احفظ الشيفرة الآتية في ملف باسم printRandom.py: import random for i in range(5): print(random.randint(1, 10)) سيطبع البرنامج السابق ناتجًا يشبه الناتج الآتي حين تشغيله: 4 1 8 4 1 ستكون نتيجة استدعاء الدالة random.randint()‎ هي عدد صحيح عشوائي يقع بين العددين الذين مررتهما إلى الدالة كوسيطين. ولمّا كانت الدالة randint()‎ موجودةً في الوحدة random، فعليك أن تكتب الكلمة random أولًا قبل اسم الدالة لتخبر بايثون أنك تريد استخدام الدالة الموجودة في الوحدة random، ونفصل بين اسم الوحدة واسم الدالة بنقطة. هذا مثال لعبارة استيراد تستورد أربع وحدات في آنٍ واحد: import random, sys, os, math يمكننا الآن استخدام أي دالة موجودة في الوحدات الأربع السابقة، وسنتعلم المزيد عن تلك الوحدات لاحقًا في هذه السلسلة. عبارة from import هنالك شكل بديل لعبارة import يتضمن الكلمة المحجوزة from، والتي نتبعها باسم الوحدة، ثم الكلمة المحجوزة import ثم نجمة *؛ فمثلًا from random import *‎. وحين استيراد الوحدات بهذا الشكل فلا حاجة إلى وضع السابقة random.‎ قبل أسماء الدوال التي نريد استدعاءها؛ لكن في المقابل من الأفضل كتابة الاسم الكامل للدالة مع السابقة التي تشير إلى اسم الوحدة لزيادة مقروئية الشيفرة البرمجية. إنهاء تنفيذ البرنامج حينما نشاء باستخدام الدالة sys.exit()‎ آخر مفهوم من مفاهيم بنى التحكم التي سنشرحها في هذا المقال هو آلية إنهاء تنفيذ البرنامج. ينتهي تنفيذ البرامج دومًا حينما يصل التنفيذ إلى نهاية الملف، لكن يمكننا إنهاء تنفيذ البرنامج قبل آخر تعليمة برمجية باستخدام الدالة sys.exit()‎. ولمّا كانت هذه الدالة جزءًا من الوحدة sys فعلينا أن نستورد تلك الوحدة في بداية البرنامج قبل استخدامها. افتح محرر الشيفرات واكتب ما يلي واحفظه exitExample.py: import sys while True: print('Type exit to exit.') response = input() if response == 'exit': sys.exit() print('You typed ' + response + '.') شغل البرنامج السابق وستجد أنك دخلت في حلقة تكرار لا نهائية دون وجود عبارة break داخلها، والطريقة الوحيدة لكي ينتهي تنفيذ البرنامج هي الوصول إلى استدعاء الدالة sys.exit()‎، ولأن قيمة المتغير response تساوي ما يدخله المستخدم عبر input()‎، فإذا أدخلت exit فستتحقق العبارة الشرطية if وسينتهي تنفيذ البرنامج. برنامج قصير: احزر الرقم جميع الأمثلة السابقة كانت بسيطة جدًا لكنها مناسبة لاستيعاب المفاهيم البرمجية الأساسية، لكن لنبني الآن برنامجًا متكاملًا نوظِّف فيه المعلومات التي تعلمناها. سنكتب في هذا القسم لعبة «احزر الرقم»، والتي ستبدو كما يلي حين تشغيلها: I am thinking of a number between 1 and 20. Take a guess. 10 Your guess is too low. Take a guess. 15 Your guess is too low. Take a guess. 17 Your guess is too high. Take a guess. 16 Good job! You guessed my number in 4 guesses! افتح محرر الشيفرات وأدخل الشيفرة الآتية واحفظها في ملف باسم guessTheNumber.py: # This is a guess the number game. import random secretNumber = random.randint(1, 20) print('I am thinking of a number between 1 and 20.') # Ask the player to guess 6 times. for guessesTaken in range(1, 7): print('Take a guess.') guess = int(input()) if guess < secretNumber: print('Your guess is too low.') elif guess > secretNumber: print('Your guess is too high.') else: break # This condition is the correct guess! if guess == secretNumber: print('Good job! You guessed my number in ' + str(guessesTaken) + ' guesses!') else: print('Nope. The number I was thinking of was ' + str(secretNumber)) لنقسم البرنامج إلى أقسام صغيرة ونناقشها كلًا على حدة، بدءًا من أعلى الملف: # This is a guess the number game. import random secretNumber = random.randint(1, 20) يبدأ البرنامج بتعليق في أول سطر فيه يشرح ماذا يفعل البرنامج، ثم يستورد الوحدة random لكي نستطيع استخدام الدالة random.randint()‎ لتوليد رقم ليحزره اللاعب، وسنخزن الرقم العشوائي الناتج في المتغير secretNumber. print('I am thinking of a number between 1 and 20.') # Ask the player to guess 6 times. for guessesTaken in range(1, 7): print('Take a guess.') guess = int(input()) سيخبر البرنامج اللاعب أنه قد «فكر» في رقم سري ويملك اللاعب ست محاولات ليحزره، وستكون الشيفرة التي تسمح للاعب بإدخال رقم والتحقق منه موجودة في حلقة for التي ستنفذ ست مرات كحد أقصى. أول ما تفعله حلقة التكرار هو طلب كتابة تخمين للرقم السري عبر input()‎، ولأن الدالة input()‎ تعيد سلسلةً نصية وليس رقمًا صحيحًا فنمرر الناتج إلى الدالة int()‎، وثم يخزن الرقم الذي أدخله المستخدم في المتغير guess. if guess < secretNumber: print('Your guess is too low.') elif guess > secretNumber: print('Your guess is too high.') تتحقق الشروط السابقة إن كان تخمين المستخدم أصغر أو أكبر من الرقم السري، وفي كلتي الحالتين سيوفر البرنامج تلميحًا لللاعب. else: break # This condition is the correct guess! إذا لم يكن تخمين اللاعب أصغر ولا أكبر من الرقم السري فهذا يعني أنه يساويه، وفي هذه الحالة سيخرج البرنامج من حلقة for عبر break. if guess == secretNumber: print('Good job! You guessed my number in ' + str(guessesTaken) + ' guesses!') else: print('Nope. The number I was thinking of was ' + str(secretNumber)) بعد نهاية حلقة for سيتحقق البرنامج عبر عبارة if…else أن اللاعب قد خمَّن الرقم السري الصحيح، ويعرض رسالة مناسبة لكل حالة. تذكر أن الشيفرات التي تقع بعد حلقة التكرار ستنفذ بعد انتهاء تنفيذ حلقة التكرار سواءً بإكمالها 6 مرات وعدم تخمين اللاعب للرقم الصحيح، أو بالخروج منها عبر break حين تخمين الرقم السري الصحيح. سيعرض برنامجنا رسالتين نصيتين فيهما متغير يحمل قيمةً عدديةً صحيحةً guessesTaken و secretNumber، ولأننا نحاول ضم عدد صحيح إلى سلسلة نصية فيجب أن نمرر الرقم إلى الدالة str()‎ أولًا، ثم نضم السلاسل النصية عبر العامل + لتمريرها إلى الدالة print()‎ لطباعتها على الشاشة. برنامج قصير: حجرة ورقة مقص لنستخدم المفاهيم البرمجية التي تعلمناها لإنشاء لعبة «حجرة ورقة مقص» الشهيرة. سيكون ناتج تشغيل اللعبة كما يلي: ROCK, PAPER, SCISSORS 0 Wins, 0 Losses, 0 Ties Enter your move: (r)ock (p)aper (s)cissors or (q)uit p PAPER versus... PAPER It is a tie! 0 Wins, 1 Losses, 1 Ties Enter your move: (r)ock (p)aper (s)cissors or (q)uit s SCISSORS versus... PAPER You win! 1 Wins, 1 Losses, 1 Ties Enter your move: (r)ock (p)aper (s)cissors or (q)uit q افتح نافذةً جديدةً في محرر الشيفرات وأدخل الشيفرة الآتية واحفظها في ملف باسم rpsGame.py: import random, sys print('ROCK, PAPER, SCISSORS') ستتبّع هذه المتغيرات عدد مرات الربح والخسارة والتعادل wins = 0 losses = 0 ties = 0 while True: # دورة اللعبة print('%s Wins, %s Losses, %s Ties' % (wins, losses, ties)) while True: # مدخلات المستخدم print('Enter your move: (r)ock (p)aper (s)cissors or (q)uit') playerMove = input() if playerMove == 'q': sys.exit() # الخروج من البرنامج if playerMove == 'r' or playerMove == 'p' or playerMove == 's': break # الخروج من حلقة مدخلات المستخدم print('Type one of r, p, s, or q.') # عرض ما اختاره اللاعب if playerMove == 'r': print('ROCK versus...') elif playerMove == 'p': print('PAPER versus...') elif playerMove == 's': print('SCISSORS versus...') # عرض ما اختاره الحاسوب randomNumber = random.randint(1, 3) if randomNumber == 1: computerMove = 'r' print('ROCK') elif randomNumber == 2: computerMove = 'p' print('PAPER') elif randomNumber == 3: computerMove = 's' print('SCISSORS') # عرض النتيجة الربح/الخسارة/التعادل if playerMove == computerMove: print('It is a tie!') ties = ties + 1 elif playerMove == 'r' and computerMove == 's': print('You win!') wins = wins + 1 elif playerMove == 'p' and computerMove == 'r': print('You win!') wins = wins + 1 elif playerMove == 's' and computerMove == 'p': print('You win!') wins = wins + 1 elif playerMove == 'r' and computerMove == 'p': print('You lose!') losses = losses + 1 elif playerMove == 'p' and computerMove == 's': print('You lose!') losses = losses + 1 elif playerMove == 's' and computerMove == 'r': print('You lose!') losses = losses + 1 لنمعن النظر إلى الشيفرة من بدايتها: import random, sys print('ROCK, PAPER, SCISSORS') # ستتبّع هذه المتغيرات عدد مرات الربح والخسارة والتعادل wins = 0 losses = 0 ties = 0 سنستورد في البداية الوحدتين random و sys لكي نستطيع استدعاء الدالتين random.randint()‎ و sys.exit()‎، ثم سنهيئ ثلاثة متغيرات لكي نتتبع عدد مرات ربح أو خسارة أو تعادل اللاعب. while True: # دورة اللعبة print('%s Wins, %s Losses, %s Ties' % (wins, losses, ties)) while True: # مدخلات المستخدم print('Enter your move: (r)ock (p)aper (s)cissors or (q)uit') playerMove = input() if playerMove == 'q': sys.exit() # الخروج من البرنامج if playerMove == 'r' or playerMove == 'p' or playerMove == 's': break # الخروج من حلقة مدخلات المستخدم print('Type one of r, p, s, or q.') يستعمل برنامجنا حلقة while داخل حلقة while، أول حلقة هي حلقة اللعبة الأساسية، وتمثل دورًا من لعبة «حجرة ورقة مقص» في كل مرة تنفذ فيها تلك الحلقة. أما حلقة التكرار الثانية فهي تطلب من اللاعب مدخلات، وستبقى تعمل حتى يدخل المستخدم r أو p أو s أو q التي ترمز إلى حجرة rock وورقة paper ومقص scissors على التوالي وبالترتيب، أما q فتعني أن اللاعب يريد إنهاء اللعبة quit، وفي تلك الحالة ستستدعى الدالة sys.exit()‎ وسنتهي تنفيذ البرنامج. إذا أدخل المستخدم r أو p أو s فسنخرج من حلقة التكرار الثانية عبر break، وإلا فسيعود التنفيذ إلى بداية حلقة التكرار ويذكر البرنامج اللاعب أن عليه إدخال r أو p أو s أو q. # عرض ما اختاره اللاعب if playerMove == 'r': print('ROCK versus...') elif playerMove == 'p': print('PAPER versus...') elif playerMove == 's': print('SCISSORS versus...') يظهر البرنامج هنا ما اختاره اللاعب. # عرض ما اختاره الحاسوب randomNumber = random.randint(1, 3) if randomNumber == 1: computerMove = 'r' print('ROCK') elif randomNumber == 2: computerMove = 'p' print('PAPER') elif randomNumber == 3: computerMove = 's' print('SCISSORS') وهنا يظهر ما اختاره الحاسوب عشوائيًا. ولمّا كانت الدالة random.randint()‎ تعيد رقمًا عشوائيًا، فسنحتاج إلى تحويل الرقم الصحيح المخزن في المتغير randomNumber إلى حجرة أو ورقة أو مقص عبر البنية الشرطية if و elif، وبالتالي سيخزن البرنامج ما اختاره الحاسوب في المتغير computerMove ثم يطبع رسالة نصية فيها الحركة المختارة. # عرض النتيجة الربح/الخسارة/التعادل if playerMove == computerMove: print('It is a tie!') ties = ties + 1 elif playerMove == 'r' and computerMove == 's': print('You win!') wins = wins + 1 elif playerMove == 'p' and computerMove == 'r': print('You win!') wins = wins + 1 elif playerMove == 's' and computerMove == 'p': print('You win!') wins = wins + 1 elif playerMove == 'r' and computerMove == 'p': print('You lose!') losses = losses + 1 elif playerMove == 'p' and computerMove == 's': print('You lose!') losses = losses + 1 elif playerMove == 's' and computerMove == 'r': print('You lose!') losses = losses + 1 وفي النهاية، سيوازن البرنامج بين السلسلتين النصيتين الموجودتين في المتغيرين playerMove و computerMove ويظهر الناتج على الشاشة، وسيزيد قيمة أحد المتغيرات wins أو losses أو ties بما يناسب الحالة. بعد أن يصل تنفيذ البرنامج إلى النهاية فسيعود إلى بداية حلقة التكرار الرئيسية ونلعب دورًا جديدًا من اللعبة. الخلاصة بعد أن تعلمنا كيفية كتابة شروط أو عبارات تكون نتيجتها True أو False، أصبح بإمكاننا كتابة برامج تستطيع اتخاذ قرارات تحدد ما هي الشيفرات التي سينفذها البرنامج وأيها سيتخطاها. أصبح تستطيع تنفيذ الشيفرات مرارًا وتكرارًا عبر حلقات التكرار، وستستفيد من العبارتين break و continue للخروج من حلقة التكرار أو العودة إلى بدايتها مباشرةً. تسمح لنا بنى التحكم بكتابة برامج أكثر ذكاءً، وسنتعلم كتابة شيفرات أفضل عبر نوع جديد من بنى التحكم الذي سنتعرف عليه في مقال قادم، ألا وهو الدوال functions. والآن هل تستطيع تجربة الآتي؟ كتابة شيفرة تطبع Hello إذا كانت القيمة 1 مخزنة في المتغير spam، و World إذا كانت القيمة 2 في المتغير spam، و Greetings فيما عدا ذلك. كتابة برنامج قصير يطبع الأرقام من 1 إلى 10 باستخدام حلقة for. ثم اكتب برنامجًا مكافئًا له يطبع الأرقام من 1 إلى 10 باستخدام حلقة while. ترجمة -وبتصرف- للفصل Flow Control من كتاب Automate the boring stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: أساسيات لغة بايثون Python مصطلحات بايثون البرمجية أنواع البيانات والعمليات الأساسية في لغة بايثون تعلم لغة بايثون النسخة العربية الكاملة لكتاب البرمجة بلغة بايثون
  7. تمتلك لغة بايثون طيفًا واسعًا من البنى البرمجية، والدوال القياسية، وميزات رائعة لبيئات التطوير التفاعلية؛ لكن تجاهل أغلب ما ذكرته آنفًا وابدأ بتعلم ما تحتاج إليه لكتابة برامجك. لكنك ستحتاج إلى تعلم المفاهيم الأساسية للبرمجة قبل أن تفعل أي شيء، وقد تظن أن هذه المفاهيم تافهة أو مملة لكنها أساسية لتتحكم بحاسوبك كما تشاء. في هذا المقال أمثلة عديدة أنصحك أن تكتبها في الصدفة التفاعلية التي تسمى أيضًا REPL (اختصار للعبارة Read-Evaluate-Print Loop أي حلقة قراءة-تقدير القيمة-طباعة)، التي تسمح لك بتنفيذ تعليمات بايثون كل تعليمة على حدة مباشرةً وتظهر لك الناتج. الصدفة التفاعلية رائعة لتتعلم التعليمات الأساسية في بايثون، لذا أؤكد عليك أن تجربها أثناء قراءتك لهذا المقال وبالتالي ستتذكر المفاهيم المشروحة فيه تذكرًا أفضل لأنك تراها عمليًا أمامك بدل قراءتها فحسب. إدخال التعابير البرمجية في الصدفة التفاعلية يمكنك تشغيل الصدفة التفاعلية بفتح محرر Mu، الذي يفترض أنك ثبتته وفق التعليمات في المقال السابق، افتح قائمة ابدأ في ويندوز أو مجلد التطبيقات في ماك وشغِّل محرر Mu، ثم اضغط على زر New ثم احفظ الملف الفارغ باسم مثل blank.py، وحينا تحاول تشغيل الملف باستخدام الزر Run أو الضغط على زر F5 في لوحة مفاتيحك فستفتح لك الصدفة التفاعلية في الجزء السفلي من نافذة المحرر، وسترى المحث <<< أمامك. أدخل 2 + 2 في سطر الأوامر لكي تجري عملية جمع بسيطة، يجب أن تبدو الطرفية التفاعلية كما يلي: >>> 2 + 2 4 >>> نسمي 2 + 2 في بايثون بالتعبير البرمجي expression، وهي أبسط أنواع التعليمات البرمجية في اللغة، وتتألف التعابير البرمجية عادةً من قيم (مثل 2) وعوامل (operators مثل +)، وتقدر قيمتها evaluate إلى قيمة واحدة؛ وهذا يعني أنك تستطيع استخدام التعابير في أي مكان في شيفرات بايثون تستطيع فيه استخدام قيمة ما. تقدر قيمة التعبير 2 + 2 في المثال السابق إلى قيمة واحدة هي 4، واعلم أن قيمةً واحدةً لا عامل فيها مثل 4 تُعَد تعبيرًا برمجيًا في بايثون أيضًا، كما هو واضح هنا: >>> 2 2 لا ضير من الأخطاء سيفشل تشغيل البرامج التي تحتوي على شيفرات لا يفهمها الحاسوب، مما يؤدي إلى إنهيار البرنامج وظهور رسالة خطأ، ورسائل الخطأ لا تسبب مشاكل في حاسوبك، لذا لا تخف من ارتكاب الأخطاء في الشيفرات البرمجية حين تجربتك، وانهيار البرنامج crash يعني أن البرنامج توقف عن التنفيذ بشكل غير متوقع. إذا أردت أن تتعرف على المزيد من المعلومات حول الخطأ الذي ظهر لك، فعليك البحث في الإنترنت عنه، أو العودة إلى توثيق لغة بايثون. يمكنك استخدام عوامل عديدة في تعابير بايثون، فالجدول الآتي يستعرض جميع العوامل الحسابية في بايثون. العامل العملية مثال الناتج ** القوة (أو الأس) 2‎ ** 3 8 % باقي القسمة 22‎ % 8 6 // عامل قسمة الأعداد الصحيحة 22‎ // 8 2 / القسمة 22‎ / 8 2.75 * الضرب 3‎ * 5 15 - الطرح 5‎ - 2 3 + الجمع 2 + 2 4 جدول العوامل الرياضية من أعلاها إلى أدناها أولوية وأسبقية ترتيب أولوية العمليات في بايثون (تسمى أيضًا «أسبقية») مشابهة لأولويتها في الرياضيات، فتقدر قيمة المعامل ** أولًا، ثم تأتي المعاملات * و / و // و % بالترتيب حسب التعبير من اليسار إلى اليمين، ثم يأتي المعاملان + و - بالترتيب أيضًا من اليسار إلى اليمين. يمكنك استخدام الأقواس () لتغيير الترتيب إن احتجت إلى ذلك. لا تلعب المسافات الفارغة أي معنى بين العوامل في بايثون (عدا المسافة البادئة في أول السطر) لكن من المتعارف عليه استخدام فراغ واحد بينها. أدخل التعابير الآتية في الصدفة التفاعلية: >>> 2 + 3 * 6 20 >>> (2 + 3) * 6 30 >>> 48565878 * 578453 28093077826734 >>> 2 ** 8 256 >>> 23 / 7 3.2857142857142856 >>> 23 // 7 3 >>> 23 % 7 2 >>> 2 + 2 4 >>> (5 - 1) * ((7 + 1) / (3 - 1)) 16.0 كل ما عليك فعله هو كتابة التعبير البرمجي، وستتولى بايثون العمليات الحسابية وتعيد لك قيمةً واحدةً هي الناتج، كما هو موضح في الرسم الآتي: اعلم أن قواعد وضع العوامل والقيم مع بعضها بعضًا لتشكيل التعابير البرمجية من أساس لغة بايثون، ومثَلها كمثل القواعد النحوية التي تساعدنا في التواصل: هذه الجملة صحيحة قاعديًا في اللغة العربية. جملة صحيحة في اللغة العربية قاعديًا. لاحظ أن الجملة الثانية صعبة الفهم لأنها لا تتبع القواعد الأساسية لبنية الجملة العربية، وبالمثل إذا أدخلت تعليمة بايثون غير صحيحة فلن تتمكن بايثون من فهمها وسيظهر خطأ SyntaxError كما هو ظاهر هنا: >>> 5 + File "<stdin>", line 1 5 + ^ SyntaxError: invalid syntax >>> 42 + 5 + * 2 File "<stdin>", line 1 42 + 5 + * 2 ^ SyntaxError: invalid syntax يمكنك دومًا اختبار صحة تعليمة ما بإدخالها في الصدقة التفاعلية، وأؤكد لك أنها لن تسبب ضررًا بحاسوبك، فأسوأ ما يمكن هو ظهور رسالة خطأ. صدقني حينما أخبرك أن المطورين المحترفين يرون رسائل الخطأ في كل يوم. أنواع البيانات العددية والعشرية والنصية أذكرك أن التعابير هي قيم تجمعها المعاملات، وتكون نتيجتها هي قيمة واحدة دومًا. نوع البيانات data type هو تصنيف للقيم، فكل قيمة يكون لها نوع بيانات واحد فقط، وسنذكر في الجدول الآتي أكثر أنواع البيانات شيوعًا. القيمة ‎-2 و 30 هي أعداد صحيحة integer أو اختصارًا int، والتي تشير إلى أنها أعداد كاملة دون فواصل ويمكن أن تكون موجبة أو سالبة؛ أما الأرقام مع فاصلة عشرية مثل 3.14 فهي تسمى «الأرقام ذات الفاصلة العائمة» floating-point numbers أو اختصارًا floats. انتبه إلى أن القيمة 42 هي عدد صحيح int، بينما 42.0 هي قيمة ذات فاصلة عائمة float. نوع البيانات أمثلة أرقام صحيحة -2, -1, 0, 1, 2, 3, 4, 5 أرقام ذات فاصلة عائمة -1.25, -1.0, -0.5, 0.0, 0.5, 1.0, 1.25 سلاسل نصية 'a', 'aa', 'aaa', 'Hello!', '11 cats'‎ جدول أنواع البيانات الشائعة يمكن أن تحتوي برامج بايثون على نصوص أيضًا، وتسمى بالسلاسل النصية strings أو اختصارًا strs. احرص على إحاطة السلاسل النصية التي تضعها في برامج بعلامة اقتباس مفردة ' كما في 'مرحبًا' أو 'مع السلامة' لكي تعرف بايثون أين تبدأ السلسلة النصية وأين تنتهي. يمكنك أيضًا إنشاء سلاسل نصية فارغة بكتابة ''، سنتعلم السلاسل النصية تفصيليًا في مقال لاحق. إذا رأيت رسالة الخطأ SyntaxError: EOL while scanning string literal فمن المرجح أنك نسيت علامة الاقتباس المفردة في نهاية السلسلة النصية، كما في المثال الآتي: >>> 'Hello, world! SyntaxError: EOL while scanning string literal ضم السلاسل النصية وتكرارها قد يتغير معنى العوامل اعتمادًا على أنواع بيانات القيم التي تحيط بها، فمثلًا العامل + هو عامل الجمع حينما يحاط بقيمتين عدديتين سواءً كانتا أعدادًا صحيحةً أو ذات فاصلة عائمة؛ لكن حين استخدام العامل + بين قيمتين نصيتين فهو يلمهما على بعضهما ويضمهما ويسمى عامل string concatenation. أدخل ما يلي إلى الصدفة التفاعلية: >>> 'Hello' + 'World' 'HelloWorld' ينتج من التعبير السابق قيمة واحدة وهي سلسلة نصية تجمع النص الموجود في السلسلتين النصيتين المدخلتين؛ لكنك إذا جربت استخدام العامل + على سلسلة نصية ورقم صحيح فلن تعرف بايثون ماذا عليها أن تفعل هنا، وستظهر لك رسالة خطأ: >>> 'Hello' + 42 Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> 'Hello' + 42 TypeError: can only concatenate str (not "int") to str رسالة الخطأ can only concatenate str (not "int") to str تعني أن بايثون تظن أنك تحاول ضم عدد صحيح 42 إلى سلسلة نصية 'Hello'، فإذا كنت تريد فعل ذلك فعليك أن تحول نوع البيانات العددي إلى نصي يدويًا لأن بايثون لن تفعل ذلك تلقائيًا بالنيابة عنك. سنتعلم تحويل أنواع البيانات في قسم «فهم مكونات تطبيقك الأول» حينما نتحدث عن الدوال str()‎ و int()‎ و float()‎. العامل * يضبط عددين صحيحين أو ذوي فاصلة عائلة مع بعضها بعضًا، لكن حين استخدامه مع سلسلة نصية وعدد صحيح فإنه يكرهها ويسمى عامل string replication. لترى كيف يعمل، أدخل سلسلةً نصية متبوعةً بعامل * ثم رقم في الصدفة الفاعلية: >>> 'Hello' * 5 'HelloHelloHelloHelloHello' نتيجة التعبير السابق هي قيمة نصية واحدة تُكرَّر فيها السلسلة النصية الأصلية عددًا محددًا من المرات قيمته هي قيمة العدد الصحيح. يفيدنا عامل تكرار السلاسل النصية في بعض الأحيان لكن فائدته لا تقارن بفائدة عامل الضم الذي نستعمله كثيرًا. يمكن أن يستعمل عامل * مع قيمتين عدديتين لإجراء عملية ضرب، أو مع سلسلة نصية واحدة وعدد صحيح واحد لإجراء عملية تكرار؛ وإلا فستظهر لنا بايثون رسالة خطأ كما يلي: >>> 'Hello' * 'World' Traceback (most recent call last): File "<pyshell#32>", line 1, in <module> 'Hello' * 'World' TypeError: can't multiply sequence by non-int of type 'str' >>> 'Hello' * 5.0 Traceback (most recent call last): File "<pyshell#33>", line 1, in <module> 'Hello' * 5.0 TypeError: can't multiply sequence by non-int of type 'float' من المنطقي ألا تسمح لنا بايثون بهذه التعابير، إذ لا يمكنك ضرب كلمتين ببعضهما، ومن الصعب أن تستعمل عددًا عشريًا لتكرار عبارة نصية. تخزين القيم في متغيرات المتغير variable هو ما يشبه الصندوق في ذاكرة الحاسوب الذي يسمح لك بتخزين قيمة واحدة فيه، فإذا أردت تخزين قيمة ناتج أحد التعبيرات البرمجية في برنامجك لاستعمالها لاحقًا فخزنها في متغير. عبارات الإسناد ستستخدم عبارة إسناد assignment statement لتخزين القيم في متغيرات، وتتألف عبارة الإسناد من اسم المتغير وعلامة المساواة (وتسمى أيضًا عامل الإسناد) والقيمة التي نرغب بتخزينها. إذا كتبت مثلًا عبارة الإسناد spam = 42 فهذا يعني أن المتغير الذي اسمه spam سيخزِّن القيمة 42 داخله. تخيل أن المتغير هو صندوق له لافتة أو اسم، يمكنك أن تضع القيم داخله كما في الشكل الآتي: العبارة spam = 42 تخبر البرنامج أن «المتغير spam يحتوي الآن على القيم العددية 42 داخله». على سبيل المثال، أدخِل ما يلي في الصدفة التفاعلية: ➊ >>> spam = 40 >>> spam 40 >>> eggs = 2 ➋ >>> spam + eggs 42 >>> spam + eggs + spam 82 ➌ >>> spam = spam + 2 >>> spam 42 سيُهيِّئ المتغير intialize أو يُنشَأ في أول مرة تخزن فيه قيمة ➊، وبعد ذلك يمكنك استخدامه في التعابير البرمجية مع غيره من المتغيرات والقيم ➋، وحين إسناد قيمة جديدة إلى المتغير ➌ فستنسى القيمة القديمة، ولذها السب كان ناتج قيمة المتغير spam في نهاية البرنامج هو 42 بدلًا من 40، وهذا ما يسمى بإعادة كتابة قيمة المتغير overwrite. أدخل الشيفرة الآتية في الصدقة التفاعلية لتجربة إعادة الكتابة فوق سلسلة نصية: >>> spam = 'Hello' >>> spam 'Hello' >>> spam = 'Goodbye' >>> spam 'Goodbye' وكما في الصندوق في الشكل الآتي، فإن الصندوق spam يخزن القيمة 'Hello' إلى أن تبدلها إلى 'Goodbye'. تنسى القيمة القديمة حين إسناد قيمة جديدة إلى المتغير. أسماء المتغيرات يصف الاسم الجيد للمتغير البيانات التي يحتويها، فتخيل أنك انتقلت إلى منزل جديد ووضعت لافتات على صناديقك وكتبت عليها «أشياء»، وبهذا لن تعرف ما يحتويه الصندوق إلى أن تنظر داخله. وستجد أن أغلبية أمثلة هذه السلسلة (وتوثيق لغة بايثون) تستعمل أسماء عامة للمتغيرات مثل spam و eggs و olive، لكن احرص على استخدام أسماء واضحة ودالة على محتويات المتغير في برامج لتزيد من مقروئية شيفرتك. وصحيحٌ أنك تستطيع تسمية متغيراتك بأي اسم، لكن هنالك بعض القيود التي تفرضها لغة بايثون، فالجدول الآتي يحتوي على أمثلة عن أسماء المتغيرات. يمكنك أن تسمي متغيراتك بأي اسم طالما التزمت بالشروط الثلاثة الآتية: أن يكون كلمةً واحدةً دون فراغات. أن يستعمل الأرقام والأحرف والشرطة السفلية _ فقط. ألا يبدأ برقم. أسماء متغيرات صالحة أسماء متغيرات غير صالحة current_balance current-balance لا يسمح باستخدام الشرطات currentBalance current balance لا يسمح باستخدام الفراغات account4 4account لا يمكن أن يبدأ برقم TOTAL_SUM TOTAL_$UM لا يسمح باستخدام المحارف الخاصة مثل $ hello hello' لا يسمح باستخدام محارف خاصة مثل ' جدول أسماء صالحة وغير صالحة للمتغيرات أسماء المتغيرات حساسة لحالة الأحرف، وهذا يعني أن spam و SPAM و Spam و sPaM هي أربعة متغيرات مختلفة، وصحيحٌ أن الاسم Spam صالح لتسمية المتغيرات في بايثون، لكن من المتعارف عليه أن نبدأ متغيراتنا بأحرف صغيرة. سنستخدم طريقة التسمية «سنام الجمل» camel case في أمثلة هذه السلسلة بدلًا من الشرطات السفلية، أي أن المتغيرات ستكون lookLikeThis بدلًا من looklikethis. قد يشير المبرمجون الخبراء إلى أن الدليل الرسمي لتنسيق شيفرات بايثون المسمى PEP 8 يقول أن علينا استخدام الشرطات السفلية، لكنني شخصيًا أفضل كتابة المتغيرات بطريقة سنام الجمل، وأقتبس من فقرة «A Foolish Consistency Is the Hobgoblin of Little Minds» في دليل PEP 8 نفسه: برنامجك الأول صحيحٌ أن الصدفة التفاعلية جيدة لتكتب تعليمات بايثون كل واحدةً على حدة، لكن لكتابة برامج كاملة متكاملة فعليك أن تكتب التعليمات البرمجية في محرر شيفرات. محررات الشيفرات تشبه كثيرًا محررات النصوص العادية مثل المفكرة أو TextMate لكنها تحتوي على ميزات مخصصة لتسهيل كتابة الشيفرات البرمجية. ولفتح نافذة تحرير ملف جديد في محرر Mu، اضغط على زر New في الصف العلوي من النافذة. يفترض أن تظهر نافذة فيها مؤشر كتابة ينتظر منك المدخلات، لكن هذه النافذة تختلف عن الصدفة التفاعلية التي كانت تشغل تعليمات بايثون أولًا بأول حين الضغط على زر Enter. إذ يسمح لك محرر الشيفرات بإدخال تعليمات برمجية متعددة ثم حفظ الملف وتشغيل البرنامج. يمكنك معرفة الفرق بينهما بسهولة، إذ تحتوي نافذة الصدفة التفاعلية على المحث <<< فيها، بينما لا يحتويه محرر الشيفرات. حان الوقت لكتابة أول برنامج لك! حينما تظهر لك نافذة المحرر أدخل فيها الأسطر الآتية: ➊ # يرحب البرنامج بالمستخدم ويسأله عن عمره ➋ print('Hello, world!') print('What is your name?') # اسأل عن الاسم ➌ myName = input() ➍ print('It is good to meet you, ' + myName) ➎ print('The length of your name is:') print(len(myName)) ➏ print('What is your age?') # اسأل عن العمر myAge = input() print('You will be ' + str(int(myAge) + 1) + ' in a year.') بعد أن تدخل الشيفرة المصدرية السابقة فعليك حفظها لكي لا تكتبها كل مرة تشغل فيها محرر Mu. اضغط على زر Save واكتب اسم الملف hello.py ثم اضغط على Save لحفظه. أنصحك أن تحفظ برامجك التي تكتبها بين الحين والآخر أثناء كتابتها وتطويرها، فلو حدث خلل في حاسوبك أو أغلقت محرر الشيفرات خطأً فلن تخسر ما كتبته، ويمكنك استعمال اختصار الحفظ الذي هو Ctrl+S في ويندوز ولينكس و ‎⌘+S في نظام ماك. بعد أن تحفظ الملف يمكنك تشغيل برنامجك. اضغط على زر F5 في لوحة المفاتيح ويجب أن يبدأ تشغيل البرنامج في الصدفة التفاعلية. تذكر أنك عليك الضغط على زر F5 من نافذة المحرر وليس من نافذة الصدفة التفاعلية. أدخل اسمك حينما يسألك البرنامج عليه، وسيبدو ناتج تنفيذ البرنامج كما يلي: Hello, world! What is your name? Abdullatif It is good to meet you, Abdullatif The length of your name is: 10 What is your age? 5 You will be 6 in a year. >>> عندما لا يبقى أي شيفرات لتنفَّذ فسينتهي برنامج بايثون terminate، أي أنه يتوقف عن العمل، ويمكننا القول بتعبير تقني أنه يخرج exit. يمكنك إغلاق محرر النصوص بالضغط على زر X في أعلى النافذة، ولإعادة فتح برنامج سابق اضغط على File ثم Open من القائمة العلوية، وستظهر نافذة اختيار الملفات التي ستختار منها الملف hello.py وتضغط على زر Open؛ يفترض أن ترى أمامك الملف hello.py الذي كتبته وحفظته سابقًا. فهم مكونات تطبيقك الأول لنأخذ جولةً سريعة على تعليمات بايثون بعد فتحك لتطبيقك الأول في محرر الشيفرات، وذلك بالنظر إلى ما يفعله كل سطر من الشيفرة. التعليقات يسمى السطر الآتي تعليقًا comment: ➊ # يرحب البرنامج بالمستخدم ويسأله عن عمره تتجاهل لغة بايثون التعليقات، ويمكنك استخدامها لكتابة ملاحظات أو لتذكير نفسك ما الذي تحاول شيفرتك فعله. تكون أي نصوص مذكورة بعد علامة المربع # جزءًا من التعليق. يضع المطورون في بعض الأحيان رمز # في بداية أحد الأسطر البرمجية لتعطيله مؤقتًا أثناء تجربة البرنامج، وهذا يسمى بتعليق الشيفرة commenting out، ويمكن أن تستفيد من هذا حينما تحاول معرفة لم لا يعمل برنامجك، ثم تزيل رمز # عندما تريد إعادة تفعيل السطر البرمجي. تتجاهل بايثون أيضًا السطر الفارغ بعد التعليق، ويمكنك إضافة الأسطر الفارغة إلى برنامجك كيفما تشاء، وهذا يسهِّل قراءة برنامجك كما لو كنت تكتب فقرات في كتاب. الدالة print()‎ تظهر الدالة print()‎ قيمة السلسلة النصية الموجودة بين قوسين على الشاشة: ➋ print('Hello, world!') print('What is your name?') # اسأل عن الاسم السطر print('Hello, world!')‎ يعني «اطبع النص الموجود في السلسلة النصية ‎'Hello, world!'‎، فحين تنفيذ بايثون لهذا السطر فأنت تطلب منها أن تستدعي الدالة print()‎ وأن تمرِّر pass قيمة السلسلة النصية إلى تلك الدالة. القيمة التي تمرر إلى استدعاء دالة function call تسمى بالوسيط argument. لاحظ أن علامات الاقتباس لا تظهر على الشاشة، فهي إشارة متى تبدأ وتنتهي السلسلة النصية، وليست جزءًا من النص. الدالة input()‎ تنتظر الدالة input()‎ أن يدخل المستخدم نصًا عبر لوحة المفاتيح ثم يضغط على زر Enter: ➌ myName = input() نتيجة استدعاء هذه الدالة هي سلسلة نصية تطابق ما أدخله المستخدم، ثم ستُسند السلسلة النصية الناتجة عن هذا الاستدعاء إلى المتغير myName. يمكنك أن تعدّ عملية استدعاء الدالة input()‎ على أنها تعبير برمجي نتيجته هي قيمة السلسلة النصية التي أدخلها المستخدم، فإذا أدخل المستخدم 'Ahmed' فيمكنك أن تقول أن التعبير البرمجي أصبح أشبه بالتعبير myName = 'Ahmed'‎. إذا استدعيت الدالة input()‎ وظهرت لك رسالة خطأ مثل NameError: name 'Ahmed' is not defined، فهذا يعني أنك تشغل الشيفرة عبر الإصدار الثاني من بايثون وليس الثالث. طباعة اسم المستخدم يحتوي الاستدعاء التالي للدالة print()‎ على التعبير ‎'It is good to meet you, ' + myName بين القوسين: ➍ print('It is good to meet you, ' + myName) تذكر أن بايثون تقدر قيمة التعابير البرمجية وتنتهي بقيمة واحدة. فإذا احتوى المتغير myName على القيمة 'Ahmed' في السطر ➌، فستقدر قيمة التعبير السابق إلى 'It is good to meet you, Ahmed'، ثم ستمرر هذه السلسلة النصية إلى الدالة print()‎ التي تطبعها على الشاشة. الدالة len()‎ يمكنك أن تمرر سلسلةً نصيةً إلى الدالة len()‎ أو متغيرًا يحتوي على سلسلةٍ نصية، وستنتج الدالة عددًا صحيحًا يمثل عدد المحارف في السلسلة النصية: ➎ print('The length of your name is:') print(len(myName)) أجرب إدخال ما يلي إلى الصدفة التفاعلية: >>> len('hello') 5 >>> len('I like to drink good coffee') 27 >>> len('') 0 وكما في الأمثلة السابقة، ستكون نتيجة len(myName)‎ هي رقم صحيح، ثم ستمرر إلى الدالة print()‎ لطباعتها. تذكر أن الدالة print()‎ تسمح لك بطباعة أعداد أو سلاسل نصية، لكن لاحظ رسالة الخطأ التي تظهر حينما تحاول كتابة ما يلي في الصدفة التفاعلية: >>> print('I am ' + 29 + ' years old.') Traceback (most recent call last): File "<pyshell#6>", line 1, in <module> print('I am ' + 29 + ' years old.') TypeError: can only concatenate str (not "int") to str لا يأتي الخطأ من دالة print()‎ بل من التعبير الذي حاولت تمريره إلى الدالة، وستحصل على رسالة الخطأ نفسها إذا كتبت التعبير البرمجي بمفرده في الصدفة التفاعلية: >>> 'I am ' + 29 + ' years old.' Traceback (most recent call last): File "<pyshell#7>", line 1, in <module> 'I am ' + 29 + ' years old.' TypeError: can only concatenate str (not "int") to str تعطي بايثون خطأً لأن العامل + يمكن أن يستعمل لجمع رقمين مع بعضهما أو ضم سلسلتين نصيتين، لكنه لا يستطيع إضافة عدد إلى سلسلة نصية لأن ذلك ليس مسموحًا به قاعديًا في بايثون. ويمكنك حل هذه المشكلة بتحويل الرقم إلى سلسلة نصية، وهذا ما سنتعلمه في القسم التالي. الدوال str()‎ و int()‎ و float()‎ إذا أردت ضم عدد صحيح مثل 29 إلى سلسلة نصية لتمريره إلى الدالة print()‎ مثلًا، فما ستحتاج إليه هو السلسلة النصية '29' التي هي النسخة النصية من العدد 29. الدالة str()‎ تقبل أن يُمرَّر إليها عدد صحيح ثم تنتج لنا سلسلةً نصيةً تمثل هذا العدد كما يلي: >>> str(29) '29' >>> print('I am ' + str(29) + ' years old.') I am 29 years old. ولأن ناتج التعبير str(29)‎ هو '29' فسيكون ناتج التعبير ‎'I am ' + str(29) + ' years old.'‎ هو ‎'I am ' + '29' + ' years old.'‎ الذي بدوره سينتج ‎'I am 29 years old.'‎، وهذه هي القيمة التي ستمرر إلى الدالة print()‎. الدوال str()‎ و int()‎ و float()‎ ستحول القيم التي تمررها إليها إلى سلسلة نصية وعدد صحيح وعدد ذي فاصلة عائلة على التوالي وبالترتيب. جرب تحول بعض القيم في الصدفة التفاعلية وانظر ماذا سيحدث: >>> str(0) '0' >>> str(-3.14) '-3.14' >>> int('42') 42 >>> int('-99') -99 >>> int(1.25) 1 >>> int(1.99) 1 >>> float('3.14') 3.14 >>> float(10) 10.0 نستدعي في المثال السابق الدوال str()‎ و int()‎ و float()‎ ونمرر إليهم مجموعةً من القيم لها أنواع بيانات مختلفة، وسنحصل على نسخة نصية أو عددية من تلك القيم. تفيد الدالة str()‎ حينما يكون لدينا عدد صحيح أو ذو فاصلة عائمة ونريد ضمه إلى سلسلة نصية، بينما تفيد الدالة int()‎ حينما يكون لدينا رقم مخزن على شكل سلسلة نصية ونريد إجراء بعض العمليات الحسابية عليه، فمثلًا تعيد الدالة input()‎ سلسلةً نصيةً دومًا حتى لو أدخل المستخدم رقمًا، فجرب إدخال spam = input()‎ في الصدفة التفاعلية وكتابة رقم ما ثم طباعة قيمة المتغير spam: >>> spam = input() 101 >>> spam '101' لاحظ أن القيمة المخزنة في المتغير spam ليست العدد 101 بل السلسلة النصية '101'، فلو أردت إجراء أي عملية رياضية على القيمة المخزنة في spam فعليك استخدام الدالة int()‎ للحصول على عدد صحيح. سنعيد تخزين القيمة العددية للمتغير spam في المتغير spam نفسه: >>> spam = int(spam) >>> spam 101 يمكنك الآن التعامل مع المتغير spam كأي عدد صحيح: >>> spam * 10 / 5 202.0 لاحظ أنك إذا مررت قيمةً إلى الدالة int()‎ لا يمكن أن تحول إلى عدد صحيح، فستظهر لك بايثون رسالة خطأ: >>> int('99.99') Traceback (most recent call last): File "<pyshell#18>", line 1, in <module> int('99.99') ValueError: invalid literal for int() with base 10: '99.99' >>> int('twelve') Traceback (most recent call last): File "<pyshell#19>", line 1, in <module> int('twelve') ValueError: invalid literal for int() with base 10: 'twelve' تفيد أيضًا الدالة int()‎ بتحويل عدد عشري إلى عدد صحيح (مع تقريبه إلى أصغر عدد صحيح يمثله): >>> int(7.7) 7 >>> int(7.7) + 1 8 لقد استخدمنا الدالتين int()‎ و str()‎ في آخر ثلاثة أسطر من برنامجك للحصول على قيمة نوع البيانات المطلوب: ➏ print('What is your age?') # ask for their age myAge = input() print('You will be ' + str(int(myAge) + 1) + ' in a year.') مساواة النصوص والأرقام صحيحٌ أن القيمة النصية لعددٍ ما تختلف اختلافًا تامًا عن العدد الصحيح أو العدد ذي الفاصلة العائمة، لكن يمكن أن يساوي العددُ الصحيح العددَ ذا الفاصلة العائمة: >>> 42 == '42' False >>> 42 == 42.0 True >>> 42.0 == 0042.000 True تفعل بايثون ذلك لأن السلاسل النصية هي نصوص بينما الأعداد الصحيحة والأعداد ذات الفاصلة هي أعداد في نهاية المطاف. المتغير myAge يحتوي على القيمة المعادة من input()‎، ولمّا كانت القيمة المعادة من الدالة input()‎ هي سلسلة نصية دومًا حتى لو أدخل المستخدم رقمًا، فعلينا استخدام int(myAge)‎ لإعادة قيمة عددية صحيحة من السلسلة النصية الموجودة في myAge، ثم سنزيد مقدار هذا العدد بواحد في التعبير int(myAge) +1. سنمرر بعد ذلك نتيجة التعبير السابق إلى الدالة str()‎ على الشكل str(int(myAge) +1)‎، ثم سنضم القيمة النصية للتعبير السابق مع السلسلتين النصيتين ‎'You will be '‎ و ‎' in a year.'‎ وذلك للحصول على قيمة نصية واحدة، ثم ستمرر هذه القيمة النصية إلى الدالة print()‎ لطباعتها على الشاشة. لنقل مثلًا أن المستخدم أدخل السلسلة النصية '4' قيمةً للمتغير myAge عبر الدالة input()‎. ستحول السلسلة النصية '4' إلى عدد صحيح 4، ثم سيضاف 1 إليها فتصبح 5، ثم تحولها الدالة str()‎ إلى سلسلة نصية مجددًا لإضافتها إلى السلاسل النصية الأخرى، وبالتالي ستنتج لدينا النسخة النهائية التي ستطبع على الشاشة، كما هو ظاهر في الشكل الآتي: الخلاصة يمكنك أن تحسب العمليات الحسابية بالآلة الحاسبة أو تضم السلاسل النصية عبر معالج النصوص في حاسوبك، ويمكنك تكرار السلاسل النصية بسهولة بنسخها ولصقها مرارًا وتكرارًا. لكن التعابير البرمجية -وما تحتويه من مكونات مثل العوامل والقيم واستدعاءات الدوال- هي اللبنة الأساسية لبناء البرامج، وبعد أن تتعلم هذه العناصر الأساسية فستتمكن من إعطاء أوامر لبايثون لتنفذ لك أمورًا معقدة على مجموعة كبير من البيانات. من المهم أن تتذكر من هذا المقال أنواع العوامل المختلفة (+ و - و * و / و // و % و ** للعوامل الحسابية، و + و * للسلاسل النصية) وأنواع البيانات المختلفة (الأعداد الصحيحة integers والأعداد ذات الفاصلة العائمة floating-point والسلاسل النصية string). شرحنا أيضًا بعض الدوال، مثل print()‎ و input()‎ التي تتعامل مع النصوص البسيطة لطباعتها على الشاشة أو لإدخالها من لوحة المفاتيح، واستعملنا الدالة len()‎ للحصول على القيمة العددية لسلسلة نصية، وساعدتنا الدوال str()‎ و int()‎ و float()‎ في تحويل القيم المُمرَّرة إليها إلى سلاسل نصية أو أعداد صحيحة أو أعداد ذات فاصلة على التوالي. سنتعلم في المقال القادم كيف نخبر بايثون متى تنفذ الشيفرة ومتى تتجاوزها بذكاء، وكيفية تكرار جزء من الشيفرة اعتمادًا على شرط محدد، وهذا ما نسميه «بنى التحكم» مما يسمح لنا بكتابة برامج تتصرف تصرفات ذكية. الآن وبعد أن أخذت المعارف اللازمة من المقال، برأيك لماذا سيسبب التعبير الآتي خطأً؟ وكيف نحله؟ شاركنا ذلك في التعليقات. 'I have eaten ' + 99 + ' burritos.' ترجمة -وبتصرف- للفصل Python Basics من كتاب Automate the boring stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: تهيئة بيئة العمل في بايثون Python أساسيات البرمجة بلغة بايثون تعلم لغة بايثون مشاريع بايثون عملية تناسب المبتدئين النسخة العربية الكاملة لكتاب: البرمجة بلغة بايثون
  8. قبل شرح كيفية تهيئة بايثون، نود إعلامك أن هذا سيكون أول مقال من سلسلة مقالات أتمتة المهام عبر بايثون. ستشرح هذه السلسلة أساسيات البرمجة بلغة بايثون، والمهام المختلفة التي يمكن لحاسوبك أتمتتها، كما سنشاركك مشاريع عملية مع نهاية كل جزئية منه، والتي تستطيع استعمالها ودراستها ومشاركتنا تطبيقاتك لها في التعليقات. ما هي بايثون؟ بايثون هي لغة برمجة، ولغة البرمجة هي مجموعة قواعد لكتابة الشيفرة يجب الالتزام بها ليفهمها مترجم اللغة، ومترجم بايثون Python interpreter هو برمجية تقرأ الشيفرات المصدرية بلغة بايثون وتطبق التعليمات البرمجية فيها. يمكنك أن تنزل مفسر لغة بايثون مجانًا من موقع اللغة الرسمي، الذي فيه إصدارات لأنظمة تشغيل ويندوز وماك ولينكس. إذا كنتَ تتساءل من أين أتى اسم بايثون، فهو من مجموعة كوميدية بريطانية اسمها Monty Python، ولا علاقة للثعابين والأفاعي باسمها. تنزيل وتثبيت بايثون يمكنك تنزيل بايثون لنظام تشغيل ويندوز أو ماك أو لينكس مجانًا من رابط موقع اللغة، وستعمل معك جميع التطبيقات المشروحة في هذه السلسلة معك. ستجد في موقع بايثون نسختين إحداهما 64-بت والأخرى 32-بت لكل نظام تشغيل، لذا عليك اختيار الملف الصحيح لتنزله وتثبته. إذا اشتريت حاسوبك بعد عام 2007 فمن المرجح أنك تستعمل نظام 64-بت، وإلا فعليك استخدام نسخة 32-بت. إذا كنت تستعمل نظام ويندوز فنزِّل مثبت بايثون (الذي ينتهي باللاحقة ‎.exe) وشغِّله، ثم اتبع ما يرشدك إليه المثبِّت على الشاشة. أما على نظام macOS فنزِّل ملف ‎.pkg ثم شغله واتبع الإرشادات الموجودة على الشاشة. أما إذا كنت تستعمل توزيعة أوبنتو (أو ما يشبهها من التوزيعات المبنية على دبيان) فيمكنك تثبيت بايثون من سطر الأوامر بتنفيذ الأمر الآتي: sudo apt install python3 python3-pip idle3 تنزيل وتثبيت محرر Mu صحيحٌ أن مفسِّر بايثون هو البرنامج الذي يشغل شيفرات بايثون التي تكتبها، لكن برمجية Mu editor هي مكان إدخالك للشيفرات، بنفس الطريقة التي تكتب فيها بمعالج النصوص المفضل لديك. يمكنك تنزيل محرر Mu من موقعه. نزِّل المثبِّت الخاص بنظامك إن كنت تستعمل ويندوز أو ماك، ثم ابدأ عملية التثبيت بفتح المثبت، أما على لينكس فستحتاج إلى تثبيت محرر Mu كحزمة بايثون، وفي تلك الحالة اتبع الإرشادات الموجودة في الموقع. تشغيل محرر Mu بعد انتهاء تثبيت Mu، يمكنك تشغيله: في نظام ويندوز بالضغط على قائمة ابدأ ثم البحث عن Mu. في نظام MacOS بفتح Finder ثم Applications ثم الضغط على أيقونة mu-editor. في لينكس عليك تشغيل الطرفية Terminal ثم كتابة mu-editor. وحين تشغيلك لمحرر Mu لأول مرة فستظهر لك نافذة تخيّرك بين عدة خيارات وعليك اختيار Python 3، يمكنك في أي وقت تغيير النمط بالضغط على زر Mode في أعلى نافذة المحرر. تشغيل IDLE نستعمل في هذه السلسلة Mu كمحرر وصدفة تفاعلية interactive shell، لكنك تستطيع استخدام أي محرر تشاء لكتابة شيفرات بايثون، وتثبت بيئة التطوير والتعليم المدمجة Integrated Development and Learning Environment ‏(اختصارًا IDLE) مع بايثون تلقائيًا، ويمكنها أن تعمل كمحرر ثانوي لك إن لم يعمل يثبت عندك محرر Mu. لنشغل IDLE: في نظام ويندوز بالضغط على قائمة ابدأ ثم البحث عن IDLE. في نظام macOS بفتح Finder ثم Applications ثم الضغط على أيقونة Python. في لينكس عليك تشغيل الطرفية Terminal ثم كتابة idle3. الصَدَفة التفاعلية Interactive Shell عندما تشغل Mu فستظهر أمامك نافذة تحرير الملف، ويمكنك فتح الصَدَفة التفاعلية بالضغط على زر REPL. الصَدَفة Shell هي برنامج يسمح لك بإعطاء تعليمات للحاسوب، كما تفعل حينما تفتح الطرفية Terminal في لينكس أو ماك، أو موجه الأوامر في ويندوز. تسمح لك الصدفة التفاعلية في بايثون بإدخال التعليمات لينفذها مفسِّر بايثون مباشرةً. تجد الصدفة التفاعلية في محرر Mu في إطار في الجزء السفلي من النافذة فيها النص الآتي: Jupyter QtConsole 4.7.7 Python 3.6.9 (default, Mar 15 2022, 13:55:28) Type 'copyright', 'credits' or 'license' for more information IPython 7.16.3 -- An enhanced Interactive Python. Type '?' for help. In [1]: إذا كنت تستعمل IDLE، فالصدفة التفاعلية هي أول ما ستراه أمامك، ويفترض أن تكون فارغة باستثناء نص يبدو كما يلي: Python 3.6.9 (default, Mar 15 2022, 13:55:28) [GCC 8.4.0] on linux Type "help", "copyright", "credits" or "license()" for more information. >>> الأسطر التي تبدأ بالعبارة In [1]:‎ أو ‎<<< تسمى محثًا prompt (أي أنها عبارة «تحثّ» المستخدم على كتابة أمر وراءها). أمثلة هذه السلسلة ستستخدم <<< للإشارة إلى محث الصدفة التفاعلية لأنه أكثر شيوعًا. إذا شغلت مفسر بايثون من سطر الأوامر فستجد أمامك المحث <<< أيضًا. يستخدم محرر Jupyter Notebook الشهير المحث ‎In [1]:‎ وشاع بسببه. لنجرب مثالًا بسيطًا، أدخل السطر الآتي في الصدفة التفاعلية بعد المحث: >>> print('Hello, world!') بعد كتابة السطر السابق فاضغط على زر Enter، ويجب أن يظهر ما يلي في الصدفة: >>> print('Hello, world!') Hello, world! لقد أعطيت الحاسوب أمرًا ونفذه لك. مبارك! تثبيت وحدات خارجية بعض البرامج التي سنكتبها ستستورد وحدات modules، وتأتي بعض هذه الوحدات مع بايثون لكن هنالك وحدات أخرى خارجية طورها مبرمجون ليسوا من فريق تطوير لغة بايثون. يوضح الملحق أ بالتفصيل كيفية استخدام البرنامج pip في ويندوز أو pip3 في ماك ولينكس لتثبيت وحدات خارجية. أين تجد المساعدة يميل المطورون إلى التعلم بالبحث في الإنترنت عن إجابات عن أسئلتهم، وهذه الطريقة مختلفة كثيرًا عمّا اعتاد كثيرون على فعله من حضور دروس لمدرس يلقي محاضرةً ويجيب عن أسئلة الطلاب. ما يميز استخدام الإنترنت للتعلم أنك ستجد مجتمعات كاملة فيها أشخاص يستطيعون الإجابة عن أسئلتك، بل ومن المرجح أن تكون أسئلتك مجاب عنها مسبقًا وتنتظرك الإجابات عنها لتقرأها. إذا واجهت رسالة خطأ أو حدث خطبٌ ما أثناء محاولتك تشغيل شيفرتك، فلن تكون أول شخص يواجه هذه المشكلة، وأؤكد لك أن العثور على حلها أسهل مما تظن بكثير. لنسبب خطأً عمدًا في برنامجنا للتجربة: أدخل ‎'42' + 3 في الصدفة التفاعلية، لا حاجة إلى شرح ما الذي تفعله هذه الشيفرة الآن لأننا سنتعلمها لاحقًا، لكن سيظهر لك الخطأ الآتي: >>> '42' + 3 ➊ Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> '42' + 3 ➋ TypeError: Can't convert 'int' object to str implicitly >>> رسالة الخطأ ➋ تظهر لأن بايثون لا تستطيع فهم التعليمة التي أعطيتها إياها، والجزء الأول ➊ من رسالة الخطأ يبين لنا التعليمة الخطأ ورقم سطرها وما المشكلة التي واجهتها بايثون معها. إذا لم تكن متأكدًا من معنى رسالة الخطأ فابحث عنها في الويب. أدخل "TypeError: Can’t convert ‘int’ object to str implicitly" (بما فيها علامات الاقتباس) في محرك البحث المفضل لديك وستجد عشرات الصفحات التي توضح لك ما هذا الخطأ وما مسبباته كما في الصورة الآتية: الشكل 2: نتائج بحث جوجل حول رسالة خطأ برمجي ستجد أن أحدهم كان له نفس سؤالك وأتى مطور من أولي الخبرة وأجابه. اعلم أنه لا يمكن لشخص واحد أن يعرف كل شيء عن البرمجة، لذا يكون البحث عن إجابات الأسئلة التقنية جزءًا من يومك كمطور برمجيات. طرح أسئلة برمجية ذكية إذا لم تجد إجابةً عن سؤالك في الإنترنت، فيمكنك أن تجرب سؤال الخبراء في أحد المنتديات أو المواقع المخصصة مثل Stack Overflow أو مجتمع Learn Programming في Reddit. لكن أبقِ في ذهنك أن هنالك طرائق ذكية لطرح الأسئلة البرمجة لتساعد الآخرين على مساعدتك. فبدايةً احرص على قراءة قسم الأسئلة الشائعة FAQ في تلك المواقع لتعرف الطريقة الصحيحة لطرح السؤال. حين طرحك لسؤال برمجي فتذكر ما يلي: اشرح ما تحاول فعله، وليس ما فعلته. هذا يسمح لمن يريد مساعدته أن يخبرك إن كنت على الطريق الصحيح لحل المشكلة. حدد النقطة التي يحدث الخطأ عندها، هل تحدث مثلًا حين بدء تشغيل البرنامج كل مرة أم حين وقوع حدث معين؟ وما هو هذا الحدث الذي يحدث ويسبب ظهور الخطأ. انسخ والصق رسالة الخطأ كاملة على مواقع تسمح لك بمشاركة الشيفرات مع الآخرين بسرعة مثل pastebin أو gits.github أو قسم الأسئلة والأجوبة في أكاديمية حسوب، فعبرها تستطيع مشاركة نصوص طويلة دون أن تفقد تنسيق النص، وبعد ذلك ضع رابط URL التابع للشيفرة التي شاركتها في مشاركتك أو سؤالك. اشرح ما حاولت فعله لحل المشكلة، وبهذا تخبر الآخرين أنك بذلت جهدًا لمحاولة حل المشكلة بنفسك. ضع إصدار بايثون الذي تستخدمه (إذ هنالك اختلافات جوهرية بين الإصدار الثاني من مفسر بايثون والإصدار الثالث). اذكر أيضًا نوع نظام تشغيلك وإصداره. إذا حدث الخطأ بعد إجراء تعديل على شيفرتك فاذكر ما هو التعديل الذي أجريته. أرجو منك أيضًا أن تتبع آداب طرح النقاشات في الإنترنت، فاحرص على سلامة السؤال من الأخطاء الإملائية أو اللغوية، وأن تكون لغته واضحة للجميع، وإذا طرحت سؤالك بالإنكليزية فلا تضعه بأحرف كبيرة، ولا تقدم مطالب غير معقولة وغير منطقية ممن يمد إليك يد المساعدة. الخلاصة يرى أغلبية مستخدم الحاسوب أن حاسوبهم هو صندوق سحري بدل رؤيته كأداة يمكنهم استخدامها كيفما يشاؤون؛ وبتعلمك البرمجة ستصل إلى أقوى أدوات المتاحة في عالمنا الحالي وهي القدرة الحاسوبية الهائلة المتوافرة أمامك! وتذكر أن تستمتع بوقتك أثناء البرمجة وتعلمها، فالبرمجة ليست كالجراحة العصبية فلا بأس أن تخطئ وتجرب كما تشاء. نفترض في هذه السلسلة أنك لا تعرف شيئًا عن البرمجة وستتعلم فيه الكثير، لكن إن كانت لديك أسئلة خارج سياقه فتذكر أن تبحث عنها أو أن تطرحها على الخبراء بأسلوب واضح فالبحث عن الإجابات من أهم الأدوات التي عليك احترافها لتعلم البرمجة. ترجمة -وبتصرف- لمقدمة كتاب Automate the boring stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا تعلم لغة بايثون تعرف على أبرز مميزات لغة بايثون تطبيقات لغة بايثون النسخة العربية الكاملة لكتاب البرمجة بلغة بايثون
×
×
  • أضف...