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

الطرق البايثونية في استخدام قواميس بايثون ومتغيراتها وعاملها الثلاثي


محمد الخضور

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

تُعد القواميس dictionaries نواة العديد من برامج بايثون نظرًا للمرونة التي توفرها أزواج القيم المفتاحية key-value pairs عبر ربط أجزاء البيانات ببعضها بعضًا، وبالتالي من المفيد أن تتعلم حول اصطلاحات قواميس بايثون الأكثر استخدامًا في الشيفرات، ويمكنك مراجعها بقراءة مقال فهم القواميس في بايثون.

استخدام التابعين ()get و ()setdefault للتعامل مع القواميس

لدى محاولتك الوصول إلى مفتاح أحد القواميس دون وجوده، سيتسبب ذلك بخطأ من النوع KeyError، لذا يلجأ المبرمجون عادةً إلى تجنُّب هذه الحالة بكتابة شيفرات غير بايثونية، كما في المثال التالي:

>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' in numberOfPets: # Check if 'cats' exists as a key.
...     print('I have', numberOfPets['cats'], 'cats.')
... else:
...     print('I have 0 cats.')
...
I have 0 cats.

تتحقق الشيفرة السابقة من وجود السلسلة النصية cats مثل مفتاح ضمن القاموس المسمى numberOfPets؛ فإذا كانت كذلك فعلًا، يُطبع محتوى القاموس الموافق لهذا المفتاح باستخدام التعليمة ['numberOfPets['cats مثل جزء من الرسالة المعروضة للمستخدم باستخدام الدالة ()print؛ وفي حال عدم وجود هذا المفتاح تُطبع عبارة باستخدام دالة ()print أخرى دون الوصول إلى القاموس numberOfPetsكون المفتاح غير موجود وبالتالي لا تسبب بوقوع الخطأKeyError`.

تمتلك قواميس بايثونتابع ()get، الذي يسمح بتحديد قيمة افتراضية لتعاد في حال طلب مفتاح غير موجود في القاموس. والشيفرة البايثونية التالية تكافئ الشيفرة في المثال السابق:

>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> print('I have', numberOfPets.get('cats', 0), 'cats.')
I have 0 cats.

يتحقق الاستدعاء (numberOfPets.get('cats', 0 من كون المفتاح cats موجودًا في القاموس numberOfPets، فإذا كان موجودًا فعلًا يعيد الاستدعاء القيمة من القاموس الموافقة للمفتاح cats، وإلا يعيد الاستدعاء الوسيط الثاني وهو في حالتنا 0. إذًا، يمثّل استخدام التابع ()get لتحديد قيمة افتراضية لتعاد في حال استدعاء مفتاح غير موجود في القاموس طريقةً أقصر وأكثر مقروئية من استخدام العبارة الشرطية if-else.

قد ترغب بالمقابل بتعيين قيمة افتراضية في حال عدم وجود مفتاح ما. على سبيل المثال، بفرض أن القاموس numberOfPets لا يتضمن المفتاح 'cats'، ستتسبب التعليمة numberOfPets['cats'] += 10 بخطأ من النوع KeyError، وقد ترغب بإضافة شيفرة مهمتها التحقق من غياب مفتاح محدد مُعينةً قيمة افتراضية على النحو التالي:

>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' not in numberOfPets:
...     numberOfPets['cats'] = 0
...
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10

ولكن بما أن هذا النمط شائع الاستخدام أيضًا، فالقواميس تتضمن تابعًا أكثر بايثونية وهو ()setdefault. تكافئ الشيفرة التالية تلك الموجودة في المثال السابق:

>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> numberOfPets.setdefault('cats', 0) # Does nothing if 'cats' exists.
0
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10

فبدلًا من استخدام جملة if شرطية للتحقق من وجود مفتاح ما في قاموس وتعيين قيمة افتراضية في حال غيابه، استخدم التابع ()setdefault.

استخدام الصنف collections.defaultdict لتعيين قيم القواميس الافتراضية

يتيح استخدام الصنف collections.defaultdict إمكانية التخلص التام من أخطاء KeyError، إذ يُمكّنك من إنشاء قاموس افتراضي عن طريق استيراد الوحدة collections واستدعاء الدالة ()collections.defaultdict مُمررًا إليها نمط معطيات تختاره ليكون قيمةً افتراضية. على سبيل المثال، في حال تمرير الدالة int مثل وسيط إلى الدالة ()collections.defaultdict تكون قد أنشأت كائنًا شبيهًا بالقاموس dictionary-like object يستخدم القيمة 0 قيمةً افتراضية في حال طلب مفاتيح غير موجودة في القاموس. دعنا نكتب التالي في الصدفة التفاعلية:

>>> import collections
>>> scores = collections.defaultdict(int)
>>> scores
defaultdict(<class 'int'>, {})
>>> scores['Al'] += 1 # No need to set a value for the 'Al' key first.
>>> scores
defaultdict(<class 'int'>, {'Al': 1})
>>> scores['Zophie'] # No need to set a value for the 'Zophie' key first.
0
>>> scores['Zophie'] += 40
>>> scores
defaultdict(<class 'int'>, {'Al': 1, 'Zophie': 40})

مرّرنا في الشيفرة السابقة الدالة ()int ولم نستدعيها، وهذا سبب إهمالنا لأقواسها إذ كتبناها بالشكل int ضمن استدعاء الدالة (collections.defaultdict(int. يمكن تمرير الدالة list مثل وسيط وبالتالي استخدام قائمة فارغة بمثابة قيمة افتراضية للقاموس، كما في المثال التالي:

>>> import collections
>>> booksReadBy = collections.defaultdict(list)
>>> booksReadBy['Al'].append('Oryx and Crake')
>>> booksReadBy['Al'].append('American Gods')
>>> len(booksReadBy['Al'])
2
>>> len(booksReadBy['Zophie']) # The default value is an empty list.
0

إذًا، في الحالات التي نحتاج فيها إلى قيمة افتراضية لكل احتمالية ممكنة للمفاتيح، فمن الأسهل استخدام الدالة ()collections.defaultdict بدلًا من استخدام قاموس عادي واستدعاء التابع ()setdefault.

استخدام القواميس بدلا من العبارة Switch

تمتلك لغات البرمجة مثل لغة جافا العبارة البرمجية switch، والتي تعد أحد أنواع العبارة الشرطية if-elif-else، إذ أنها تشغِّل الشيفرة بناءً على القيمة التي يتضمنها متغير معين من بين مجموعة قيم، أما لغة بايثون فلا تتضمن العبارة switch ما يجعل مبرمجي بايثون يكتبون في بعض الحالات شيفراتٍ كما في المثال التالي، والذي يشغّل مجموعة من تعليمات الإسناد على ضوء القيمة التي يتضمنها المتغير season من بين مجموعة قيم، على النحو التالي:

# All of the following if and elif conditions have "season ==":
if season == 'Winter':
    holiday = 'New Year\'s Day'
elif season == 'Spring':
    holiday = 'May Day'
elif season == 'Summer':
    holiday = 'Juneteenth'
elif season == 'Fall':
    holiday = 'Halloween'
else:
    holiday = 'Personal day off'

لا يمكن عدّ الشيفرة السابقة غير بايثونية تمامًا، لكنها طريقة طويلة قليلًا، ومن الجدير بالذكر هنا أن عبارة switch في لغة جافا تفرض في سياقها استخدام تعليمة break في نهاية كل من كتلها، وبدونها سيتابع البرنامج تنفيذ الكتلة التالية الخاصة بالحالة التالية لقيمة المتغير، وبالتالي يمثّل نسيان استخدام التعليمة break مصدرًا شائعًا للأخطاء. في المقابل، تتكرر العبارات if-elif في مثالنا السابق، وهذا ما يجعل بعض المبرمجين يفضلون إنشاء قاموس بدلًا عنها. فيما يلي صيغة بايثونية مختصرة لنفس المثال السابق:

holiday = {'Winter': 'New Year\'s Day',
           'Spring': 'May Day',
           'Summer': 'Juneteenth',
           'Fall':   'Halloween'}.get(season, 'Personal day off')

تتضمن الشيفرة السابقة تعليمة إسناد واحدة، إذ تمثّل القيمة المُخزّنة في المتغير holiday القيمة المعادة من استدعاء التابع ()get، الذي يعيد القيمة الموافقة للمفتاح المُعيّن في المتغير season، وفي حال كون المفتاح المطلوب غير موجود، سيعيد التابع ()get القيمة الافتراضية 'Personal day off'. جعل استخدام قاموس من الشيفرة مختصرة، إلا أنها قد تكون أصعب قليلًا للقراءة، ويعود القرار حول استخدامها من عدمه لك.

التعابير الشرطية: عامل بايثون الثلاثي القبيح

تعمل العوامل الثلاثية Ternary operators (والتي تُسمّى في بايثون رسميًا بالتعابير البرمجية الشرطية أو تعابير الانتقاء الثلاثية) على تقييم تعبير ما إلى إحدى قيمتين بناءً على شرط ما، وهذا ما نفعله عادةً باستخدام التعبير البايثوني if-else، على النحو التالي:

>>> # Pythonic Example
>>> condition = True
>>> if condition:
...     message = 'Access granted'
... else:
...     message = 'Access denied'
...
>>> message
'Access granted'

تعني كلمة "ثلاثي Ternary" ببساطة أن العامل يستقبل ثلاثة مدخلات، إلا أن مرادفها في عالم البرمجة هو "التعبير الشرطي". تقدّم التعابير الشرطية صيغةً مختصرة تُكتب ضمن سطر واحد، وهذا الأمر مناسب لهذا النمط الثلاثي، وتُنفّذ هذه التعابير في بايثون من خلال توزيع معيّن لكل من الكلمتين المفتاحيتين ifو else، كما في المثال التالي:

>>> valueIfTrue = 'Access granted'
>>> valueIfFalse = 'Access denied'
>>> condition = True
1 >>> message = valueIfTrue if condition else valueIfFalse
>>> message
'Access granted'
2 >>> print(valueIfTrue if condition else valueIfFalse)
'Access granted'
>>> condition = False
>>> message = valueIfTrue if condition else valueIfFalse
>>> message
'Access denied'

يُقيّم التعبير ذو الرقم 1 إلى القيمة الموافقة للوسيط valueIfTrue في حال كون متغير الشرط condition محققًا، أي مُقيّم على أنه True؛ أما في حال كونه غير محقق، أي أنه مُقيّم على أنه False فيُقيّم التعبير إلى القيمة الموافقة للوسيط valueIfFalse. وصف مبتكر لغة بايثون هذا التعبير مازحًا بأنه "قبيح عمدًا intentionally ugly"، إذ تضع معظم لغات البرمجة المُتضمنة العوامل الثلاثية الشرط بدايةً متبوعًا بالتعليمات المطلوبة في حال تحققه ثم تلك المطلوبة في حال عدم تحققه، ولكن يمكن -مع هذه الطريقة- استخدام تعبير شرطي في أي موضع نريد مكان أي قيمة أو تعبير، حتى مثل وسيط لإحدى الدوال كما هو الحال في السطر 2 من الشيفرة السابقة.

ما هو سبب اعتماد بايثون لهذه الصياغة في الإصدار 2.5 منها رغم كونها تنافي المبدأ التوجيهي الأول القائل "الجمال أفضل من القبح"؟ السبب هو أن العوامل الثلاثية شائعة الاستخدام بين المبرمجين رغم كونها ذات مقروئية منخفضة نسبيًا، لذلك طالبوا بايثون بدعمها. جديرٌ بالذكر أنه من الممكن إساءة استخدام اختصار العوامل المنطقية بغية إنشاء نوع من أنواع العوامل الثلاثية، إذ سيُقيّم تعبير مثل:

condition and valueIfTrue or valueIfFalse

إلى القيمة الموافقة للتعبير valueIfTrue في حال تحقق الشرط وإلى valueIfFalse في حال عدم تحققه (ماعدا حالة خاصة مهمة). لاحظ المثال التالي:

>>> # Unpythonic Example
>>> valueIfTrue = 'Access granted'
>>> valueIfFalse = 'Access denied'
>>> condition = True
>>> condition and valueIfTrue or valueIfFalse
'Access granted'

ينطوي العامل الثلاثي الزائف هذا على خطأ غامض خاص يحدث عند كون قيمة التعبير valueIfTrue من النمط الخاطئ (بمعنى أنها 0 أو False أو None أو سلسلة نصية فارغة)، فعندها سيُقيّم التعبير بصورة غير متوقعة إلى القيمة الموافقة للتعبير valueIfFalse عند تحقُق الشرط.

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

ليست التعابير الشرطية بايثونية تمامًا وليست غير بايثونية في الوقت نفسه، ففي حال استخدمتها، تجنب تداخلها:

>>> # Unpythonic Example
>>> age = 30
>>> ageRange = 'child' if age < 13 else 'teenager' if age >= 13 and age < 18 else 'adult'
>>> ageRange
'adult'

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

التعامل مع قيم المتغيرات

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

الإسناد التسلسلي وعوامل المقارنة

في حال أردت التحقق من كون عدد ما ينتمي إلى مجال محدد، ستستخدم غالبًا العامل المنطقي and على النحو التالي:

# Unpythonic Example
if 42 < spam and spam < 99:

إلا أن بايثون تسمح بتسلسل عوامل المقارنة وبالتالي من الممكن الاستغناء عن العامل and. تكافئ الشيفرة التالية تلك المذكورة في المثال السابق:

# Pythonic Example
if 42 < spam < 99:

ينطبق الأمر ذاته على تسلسل عامل الإسناد =، إذ من الممكن تعيين عدد من المتغيرات إلى نفس القيمة ضمن سطر برمجي واحد، على النحو التالي:

>>> # Pythonic Example
>>> spam = eggs = bacon = 'string'
>>> print(spam, eggs, bacon)
string string string

يمكن استخدام العامل and للتحقق من كون المتغيرات الثلاث تخزّن فعلًا نفس القيمة، أو استخدام سلسلة من العامل == للتحقق من المساواة ببساطة أكبر.

>>> # Pythonic Example
>>> spam = eggs = bacon = 'string'
>>> spam == eggs == bacon == 'string'
True

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

التحقق من كون قيمة متغير تنتمي لمجموعة محددة من القيم

قد تصادف أحيانًا حالة معاكسة تمامًا لتلك الواردة في الفقرة السابقة: وهي التحقق من كون قيمة متغير ما هي واحدة من مجموعة قيم ممكنة، ومن الممكن إنجاز ذلك باستخدام العامل or كما في التعبير التالي:

spam == 'cat' or spam == 'dog' or spam == 'moose` 

إلا أن الجزء المتكرر ==spam يجعل من التعبير السابق غير عملي الاستخدام، ويمكن بدلًا من ذلك وضع القيم المحتملة ضمن بنية صف والتحقق من كون قيمة متغير ما موجودةً ضمن هذا الصف باستخدام العامل in، كما في المثال التالي:

>>> # Pythonic Example
>>> spam = 'cat'
>>> spam in ('cat', 'dog', 'moose')
True

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

الخلاصة

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

تمتلك القواميس التابعين ()get و ()setdefault بغية التعامل مع المفاتيح غير الموجودة، ولكن من الممكن استخدام قاموس من النوع collections.defaultdict لتخزين القيم الافتراضية للمفاتيح غير الموجودة.

لا تتضمن لغة بايثون العبارة switch، لكن يمثّل استخدام القواميس طريقةً مختصرة لتطبيق ما يكافئ استخدام العبارة switch دون الحاجة إلى استخدام عدد كبير من عبارات if-elif-else، كما يمكن استخدام العوامل الثلاثية عند الحاجة إلى تقييم شرط ما إلى إحدى قيمتين.

وتتحقق العوامل == من كون المتغيرات متساوية أم لا، في حين يتحقق العامل in من كون المتغير يمثل واحدة من مجموعة قيم ممكنة.

ترجمة -وبتصرف- لجزء من الفصل السادس "كتابة شيفرات بايثون" من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...