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

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


محمد الخضور

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

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

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

مبادئ بايثون التوجيهية العشرون

مبادئ بايثون التوجيهية العشرون أو ما يعرف باسم "زن بايثون The Zen of Python" الموضوعة من قبل تيم بيترز Tim Peters تختص بتصميم لغة بايثون وبرامجها. وليس من الضروري أن تتبع في برامجك كامل هذه التوجيهات، إلا أنه من المفيد تذكرها دومًا. كما أنها تمثل هديةً مخفية أو طرفة كامنة تظهر لدى تشغيل الأمر import this كما يلي:

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
--snip--

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

وبالنتيجة هذه المبادئ التوجيهية لا تتعدى كونها آراء يمكن للمبرمجين أن يؤيدوها أو يعارضوها. وكأي مجموعة من المبادئ الجيدة فإنها تناقض نفسها موفرةً أكبر قدر ممكن من المرونة. وفيما يلي تفسيري لهذه الحكم:

اقتباس

الجمال أفضل من القبح

Beautiful is better than ugly

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

اقتباس

التصريح أفضل من التضمين

Explicit is better than implicit

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

اقتباس

البساطة أفضل من التعقيد، المعقد أفضل من المعقد المتشعب

Simple is better than complex. Complex is better than complicated

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

اقتباس

السطحية أفضل من التداخل

Flat is better than nested

يميل المبرمجون لتنظيم شيفرتهم ضمن فئات، لاسيما تلك الفئات التي تتضمن فئات فرعية والتي تتضمن بدورها فئات فرعية أيضًا. ولا تضيف هذه البنى الهرمية عادةً إلى الشيفرة تنظيمًا بقدر ما تضيف له بيروقراطية. ولا بأس بأن تكتب شيفرتك ضمن إحدى الوحدات عالية المستوى أو ضمن بنية معطيات واحدة. ولكن إن بدت شيفرتك بالشكل ()spam.eggs.chicken.fish أو ['spam['eggs']['chicken']['fish، فاعلم أن شيفرتك شديدة التعقيد والتشعب.

اقتباس

التباعد أفضل من التقارب

Sparse is better than dense

يميل المبرمجون عادةً إلى حشر أكبر كم ممكن من التعليمات ضمن أصغر جزء ممكن من الشيفرة، كما في السطر البرمجي:

print('\n'.join("%i bytes = %i bits which has %i possiblevalues." % (j, j*8, 256**j-1) for j in (1 << i for i in range(8))))

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

اقتباس

للمقروئية أهميتها

Readability counts

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

اقتباس

الحالات الخاصة ليست خاصة لدرجة تسمح بكسر القواعد، رغم كون الواقع العملي غير مثالي

Special cases aren’t special enough to break the rules. Although practicality beats purity

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

اقتباس

الحل ليس بإسكات الأخطاء، ما لم تُسكّت عمدًا

Errors should never pass silently. Unless explicitly silenced

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

اقتباس

احذر اغراءات التخمين في رحلة كشف الغموض

In the face of ambiguity, refuse the temptation to guess

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

اقتباس

لابد من وجود طريقة واحدة واضحة لإنجاز الأمر، ومن المفضل وجود طريقة واحدة فقط

There should be one—and preferably only one—obvious way to do it

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

اقتباس

قد لا تكون الأمور واضحة بدايةً مالم تكن هولنديًا

Although that way may not be obvious at first unless you’re Dutch

هذه الحكمة عبارة عن طرفة، إشارةً لكون مبتكر بايثون جيدو فان روسوم هولندي الأصل.

اقتباس

أن تفعلها الآن أفضل من ألا تفعلها أبدًا، رغم كون عدم فعلها أبدًا أفضل من إلزامية فعلها في نفس اللحظة

Now is better than never. Although never is often better than *right* now

تشير هاتان الحكمتان لحقيقة كون الشيفرة التي تُنفّذ ببطء هي أسوأ وضوحًا من تلك التي تُنفّذ سريعًا. ولكن بالمقابل من الأفضل الانتظار بعض الوقت لحين تنفيذ برنامجك على أن يُنفّذ بسرعة وبنتائج خاطئة.

اقتباس

تعد الفكرة سيئة في حال كون شرح كيفية تنفيذها أمر عسير، وقد تكون الفكرة جيدة في حال كون شرح كيفية تنفيذها أمرًا يسيرًا

If the implementation is hard to explain, it’s a bad idea. If the implementation is easy to explain, it may be a good idea

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

اقتباس

استخدام نطاقات الأسماء فكرة رائعة - لنستخدمها!

Namespaces are one honking great idea—let’s do more of those

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

وككل الآراء في عالم البرمجة، قد لا تتفق والآراء المبينة أعلاه أو قد تعبّر عما هو مخالف لرأيك وموقفك، ولكن تذكر دائمًا بأن الجدالات حيال كيفية كتابة الشيفرات وما يعتبر منها بايثونيًا -وعلى خلاف ما تظنه- نادرًا ما يكون مثمرًا (ما لم تكن تؤلف كتابًا كاملًا مليئًا بالآراء البرمجية).

اعتد استخدام المسافات البادئة ذات المعنى

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

وقد يبدو توزيع كتل التعليمات ضمن مجموعات بالاعتماد على المسافات البادئة أمرًا غريبًا في بايثون، إذ تبدأ الكتل وتنتهي في لغات البرمجة الأخرى باستخدام الأقواس المعقوصة { و }. ولكن حتى المبرمجين ممن يستخدمون لغات برمجة غير بايثون عادةً ما يبدأون الكتل البرمجية بمسافة بادئة كما هو الحال مع مبرمجي بايثون، ما يجعل من مقروئية شيفراتهم أعلى. فعلى سبيل المثال، لغة جافا لا تتضمّن مفهوم إلزامية استخدام المسافات البادئة، ومع ذلك يميل مستخدموها لاستخدام المسافات البادئة بغية زيادة مقروئية شيفراتهم. يتضمن المثال التالي دالة جافا باسم ()main تحتوي على استدعاء وحيد لدالة ()println:

// Java Example
public static void main(String[] args) {
    System.out.println("Hello, world!");
}

ستعمل شيفرة جافا السابقة كما يجب حتى في حال عدم استخدام مسافة بادئة لسطر الدالة ()println، وذلك لأن الأقواس المعقوصة تحدد بداية ونهاية الكتل البرمجية في جافا عوضًا عن المسافات البادئة. وعلى خلاف جافا التي جعلت من استخدام المسافات البادئة أمرًا اختياريًا، فرضت بايثون جعل الشيفرات مقروءة دومًا بإلزامية استخدام المسافات البادئة. مع ملاحظة أن بايثون لا تفرض استخدام المسافات البيضاء، إذ أنها لا تفرض أي قيود على استخدام المسافات بيضاء غير المُمثلة لمسافات بادئة (فكلا التعبيرين 2+2 و2 + 2 يعملان في بايثون).

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

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

>>> from __future__ import braces
SyntaxError: not a chance

لن تضاف الأقواس إلى بايثون على المدى المنظور.

صيغ شائعة الاستخدام على نحو خاطئ

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

استخدم الدالة ()enumerate بدلا من ()range

يستخدم بعض المبرمجون الدالتين ()range و()len لدى الحاجة إلى المرور على عناصر قائمة أو غيرها من البنى المتسلسلة بغية توليد الأرقام الصحيحة الدالة على فهرس (ترتيب) العناصر ابتداءً من الصفر ووصولًا إلى ما قبل طول السلسلة، ومن الشائع استخدام متغير باسم i للدلالة على الفهرس في حلقات for التكرارية هذه. فعلى سبيل المثال، بكتابة الشيفرة غير البايثونية في الصدفة التفاعلية سنحصل على الخرج المبين أدناه:

>>> animals = ['cat', 'dog', 'moose']
>>> for i in range(len(animals)):
...     print(i, animals[i])
...
0 cat
1 dog
2 moose

قد يكون الاصطلاح (()range(len واضح، لكنه لا يرقى لأن يعد مثاليًا نظرًا لصعوبة قراءته. فبدلًا من ذلك من الممكن تمرير القائمة أو السلسلة إلى دالة بايثون ()enumerate، والتي ستعيد عددًا صحيحًا دالًا على رقم الفهرس مع قيمة العنصر الذي يشير إليه كل فهرس. فعلى سبيل المثال، من الممكن كتابة الشيفرة البايثونية التالية وصولًا إلى نفس النتيجة السابقة:

>>> # Pythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for i, animal in enumerate(animals):
...     print(i, animal)
...
0 cat
1 dog
2 moose

وبذلك ستكون شيفرتك أفضل مع استخدام الدالة ()enumerate عوضًا عن التركيب (()range(len. وفي حال رغبتك بطباعة عناصر القائمة فقط دون رقم فهرس كل منها، فمن الممكن أيضًا المرور على عناصر القائمة بطريقة بايثونية كما يلي:

>>> # Pythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for animal in animals:
...     print(animal)
...
cat
dog
moose

فكل من استخدام الدالة ()enumerate والمرور المباشر على عناصر السلسلة هي أمور مفضلة على استخدام التركيب التقليدي (()range(len.

استخدام التعليمة with بدلا من الدالتين()open و()close

تعيد الدالة ()open كائن ملف يتضمّن التوابع اللازمة للقراءة من ملف أو الكتابة فيه، وعند انتهائك يعمل التابع ()close الخاص بكائن الملف على إتاحة الملف للبرامج الأخرى لتقرأ منه أو تكتب فيه. كما من الممكن استخدام كل من هاتين الدالتين منفردة، إلا أن هذه الطريقة ليست بايثونية. فعلى سبيل المثال، لنُدخل الشيفرات التالية في الصدفة التفاعلية بغية كتابة العبارة النصية "!Hello, World" ضمن الملف المسمى spam.txt:

>>> # Unpythonic Example
>>> fileObj = open('spam.txt', 'w')
>>> fileObj.write('Hello, world!')
13
>>> fileObj.close()

كتابة الشيفرة بهذه الطريقة قد تودي بالنتيجة إلى إبقاء الملف مفتوحًا وغير متاحًا للبرامج الأخرى، ففي حال حدوث خطأ في كتلة try مثلًا، سيتجاهل الملف استدعاء الدالة ()close كما في المثال التالي:

>>> # Unpythonic Example
>>> try:
...     fileObj = open('spam.txt', 'w')
...     eggs = 42 / 0    # A zero divide error happens here.
...     fileObj.close()  # This line never runs.
... except:
...     print('Some error occurred.')
...
Some error occurred.

فبمجرد الوصول إلى خطأ القسمة على صفر سينتقل التنفيذ مباشرةً إلى الكتلة except، متجاوزًا استدعاء الدالة ()close تاركًا بذلك الملف مفتوحًا، ما قد يؤدي بالنتيجة إلى أخطاء تلف الملفات file corruption لاحقًا، ومن الصعب تعقب الخطأ وتوقّع أن مصدره هو كتلة try.

وبدلًا من الطريقة السابقة من الممكن استخدام التعليمة with والتي تستدعي الدالة ()close تلقائيًا بمجرد انتهاء تنفيذ الكتلة with. وفيما يلي مثال مكتوب بطريقة بايثونية يؤدي نفس مهمة المثال الأول من هذه الفقرة:

>>> # Pythonic Example
>>> with open('spam.txt', 'w') as fileObj:
...     fileObj.write('Hello, world!')

فرغم عدم استدعاء الدالة ()close صراحةً، إلا أن التعليمةwith ستستدعيها نلقائيًا بمجرد انتهاء تنفيذ الكتلة البرمجية هذه.

استخدم المعامل is بدلًا من == للمقارنة مع القيمة الخالية None

يقارن معامل المساواة == قيمتي كائنين، في حين أن معامل التماثل is يقارن تطابق هويات الكائنات. فمن الممكن أن يتضمّن كائنين قيمًا متكافئة ولكن كونهما كائنان منفصلان فهذا يعني أن لكل منهما هويته المنفصلة عن الآخر. وعمومًا في حال مقارنة قيمة ما مع القيمة الخالية None استخدم دائمًا المعامل is عوضًا عن المعامل ==.

فقد يُقيّم التعبير spam==None على أنه صحيح True في بعض الحالات حتى في حال كون المتغير spam فارغًا بالمعنى المجرد، الأمر الناتج عن زيادة تحميل المعامل ==. في حين أن التعبير spam is None سيتحقق من كون القيمة في المتغير spam هي حرفيًا None، إذ أنّ None هي القيمة الوحيدة ضمن نمط المعطيات الخالية None Type، فلا يوجد سوى كائن خالٍ None Object واحد في أي برنامج بايثون. فإن عُيّن أحد المتغيرات ليكون خاليًا None، فعندها سيُقيّم التعبير is None دومًا على أنه صحيح True، وفيما يلي مثال على حالة زيادة تحميل المعامل:

>>> class SomeClass:
...     def __eq__(self, other):
...         if other is None:
...             return True
...
>>> spam = SomeClass()
>>> spam == None
True
>>> spam is None
False

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

ونهايةً، لا تستخدم المعامل is مع القيمتين True وFalse بل استخدم المعامل == لمقارنة هذه القيم، من قبيل spam == True أو Spam == False. والطريقة الأكثر شيوعًا في مثل هذه الحالات هي عدم استخدام المعامل والقيمة المنطقية بالمطلق، والاكتفاء بكتابة الشيفرة بالشكل :if spam أو :if not spam بدلًا من كتابة :if spam == True أو :if spam == False.

تنسيق السلاسل النصية

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

استخدم تنسيق السلاسل النصية الخام إذا تضمنت السلاسل عدة خطوط مائلة عكسية

تمكننا محارف الهروب escape characters من إدخال النصوص ضمن صياغة السلسلة النصية والتي يستحيل تضمينها فيها دون استخدامها. فعلى سبيل المثال لابد من استخدام محرف الهروب \ في العبارة Ahmad\'s chair لتُفسر علامة الاقتباس الثانية على أنها جزء من السلسلة النصية وليست على أنها علامة انتهاء السلسلة. ولكن ماذا لو أردنا بالفعل تضمين الرمز \ بحد ذاته ضمن السلسلة النصية؟ عندها يجب استخدامه بالشكل \\.

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

>>> # Unpythonic Example
>>> print('The file is in C:\\Users\\Al\\Desktop\\Info\\Archive\\Spam')
The file is in C:\Users\Al\Desktop\Info\Archive\Spam

أما باستخدام مفهوم السلاسل النصية الخام (لاحظ البادئة r) فسنحصل بالنتيجة على نفس السلسلة ولكن مع شيفرة ذات مقروئية أعلى:

>>> # Pythonic Example
>>> print(r'The file is in C:\Users\Al\Desktop\Info\Archive\Spam')
The file is in C:\Users\Al\Desktop\Info\Archive\Spam

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

نسق السلاسل النصية باستخدام السلاسل النصية التنسيقية F-Strings

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

'Hello, ' + name + '. Today is ' + day + ' and it is ' + weather + '.'

كما من الممكن استخدام مُحدّد التحويل ‎%s الذي يجعل من الصياغة أسهل بعض الشيء بالشكل:

'Hello, %s. Today is %s and it is %s.' % (name, day, weather)

وبالنتيجة ستعمل كلا الطريقتين على إدخال السلاسل النصية الموافقة مكان كل من المتغيرات name وday وweather في السلسلة النصية المجردة وصولًا إلى سلسلة نصية جديدة مثل:

'Hello, Al. Today is Sunday and it is sunny.'

يعمل التابع ()format الخاص بالسلاسل المحرفية على تمكين لغة تخصيص التنسيق المصغرة Format Specification Mini-Language والتي تتضمن استخدام أزواج الأقواس المعقوصة {} بطريقة مماثلة لفكرة استخدام مُحدّد التحويل s%، إلا أن هذه الطريقة تنطوي على شيء من التعقيد ما قد يُنتج شيفرة بمقروئية منخفضة، لذا لا أشجع استخدامها.

إلى أن جاء الإصدار 3.6 من بايثون بميزة السلاسل النصية التنسيقية f-strings (اختصارًا لعبارة format strings) التي توفر طريقة أكثر ملائمة لإنشاء سلاسل نصية تتضمن سلاسل نصية أخرى. وكما هو الحال مع السلاسل النصية الخام والتي نستخدم لإنشائها البادئة r قبل علامة الاقتباس الاستهلالية، نستخدم هنا البادئة f، وفيها نُضمّن أسماء المتغيرات المطلوبة ضمن أقواس معقوصة لاستبدال كل منها لاحقًا بالسلاسل النصية المُخزنة فيها، على النحو:

>>> name, day, weather = 'Al', 'Sunday', 'sunny'
>>> f'Hello, {name}. Today is {day} and it is {weather}.'
'Hello, Al. Today is Sunday and it is sunny.'

كما من الممكن تضمين تعابير برمجية كاملة في الأقواس المعقوصة، كما في المثال:

>>> width, length = 10, 12
>>> f'A {width} by {length} room has an area of {width * length}.'
'A 10 by 12 room has an area of 120.'

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

>>> spam = 42
>>> f'This prints the value in spam: {spam}'
'This prints the value in spam: 42'
>>> f'This prints literal curly braces: {{spam}}'
'This prints literal curly braces: {spam}'

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

وجود هذه الطرق المتعددة يخالف الحكمة القائلة "لابد من وجود طريقة واحدة واضحة لإنجاز الأمر، ومن المفضل وجود طريقة واحدة فقط" من مبادئ بايثون التوجيهية العشرون، إلا أن السلاسل النصية التنسيقية f-strings قد جاءت كتطوير للغة بايثون (من وجهة نظري)، وكما أن أحد المبادئ التوجيهية يشير إلى أن "الواقع العملي غير مثالي"، فإن كنت تستخدم الإصدار 3.6 من بايثون وما بعده فاستخدم دومًا السلاسل النصية التنسيقية. أما في حال استخدامك للإصدارات الأقدم، فأنصحك باستخدام التابع ()format أو الاعتماد على محدد التحويل s%.

إنشاء نسخ ضحلة عن القوائم lists

من الممكن إنشاء سلاسل نصية أو قوائم جديدة من تلك الحالية باستخدام صياغة التجزئة، ولرؤية كيفية عملها اكتب التعليمات التالية في الصدفة التفاعلية:

>>> 'Hello, world!'[7:12] # Create a string from a larger string.
'world'
>>> 'Hello, world!'[:5] # Create a string from a larger string.
'Hello'
>>> ['cat', 'dog', 'rat', 'eel'][2:] # Create a list from a larger list.
['rat', 'eel']

نضع رمز النقطتين الرأسيتين : بين فهرسي عنصر البداية والنهاية للسلسلة الجديدة المراد إنشاؤها، ولدى إهمال وضع فهرس البداية قبل النقطتين الرأسيتين كما في المثال '[Hello, world!'[:5، فيُعيّن حينها افتراضيًا إلى القيمة 0، أما إذا أهملنا فهرس النهاية بعد النقطتين الرأسيتين كما في المثال [:cat', 'dog', 'rat', 'eel'][2']، فيُعيّن افتراضيًا إلى فهرس العنصر الأخير من السلسلة الأم.

أما إذا أهملت تعيين كلا الفهرسين، فسيتم تعيين فهرس البداية إلى 0 (أي بداية القائمة أو السلسلة) وفهرس النهاية إلى نهاية القائمة، الأمر الذي يمثل طريقة فعالة لإنشاء نسخة عن السلسلة:

>>> spam = ['cat', 'dog', 'rat', 'eel']
>>> eggs = spam[:]
>>> eggs
['cat', 'dog', 'rat', 'eel']
>>> id(spam) == id(eggs)
False

ومن الجدير بالملاحظة في المثال السابق أن هويات القائمتين spam وeggs مختلفتين رغم تطابق قيمهما، إذ أن السطر البرمجي [:]eggs = spam ينشئ نسخة ضحلة عن القائمة spam، في حين أن التعليمة eggs=spam ستنسخ فعليًا مرجع القائمة spam وتسنده إلى القائمة eggs، إلا أن استخدام التعليمة [:] قد يبدو غريبًا بعض الشيء، واستخدام الدالة ()copy من وحدة النسخ copy module لإنشاء نسخة ضحلة من القائمة تعد الطريقة الأعلى مقروئية:

>>> # Pythonic Example
>>> import copy
>>> spam = ['cat', 'dog', 'rat', 'eel']
>>> eggs = copy.copy(spam)
>>> id(spam) == id(eggs)
False

فمن المفضل معرفتك لهذه الصياغة الغريبة لحالات مصادفتك لشيفرات بايثون قد استخدمتها، أما في شيفراتك الخاصة، فلا ننصحك باستخدامها. وتذكر أن كلًا من [:] و ()copy.copy تُنشآن نسخًا ضحلة.

الخلاصة

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

ولعل حجر الأساس وجوهر الشيفرات البايثونية هو "مبادئ بايثون التوجيهية العشرون"، وهي عبارة عن توجيهات عامة للكتابة بلغة بايثون. إلا أن هذه الحكم العشرون اختيارية وليست إلزامية لكتابة شيفرات بايثون، ومع ذلك من الجيد تذكرها دومًا.

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

ورغم كون العديد من مبرمجي بايثون يستخدمون التركيب (()range(len للحلقات التكرارية، إلا أن الدالة ()enumerate توفّر منهجية أوضح للحصول على رقم الفهرس والقيمة الموافقة له لدى المرور على سلسلة ما. وكذلك الأمر بالنسبة للعبارة with الأوضح والأقل تسببًا بالأخطاء للتعامل مع الملفات مقارنةً باستدعاء التابعين ()open و()close يدويًا، إذ تضمن العبارة with استدعاء التابع ()close عند انتهاء التنفيذ وخروجه من الكتلة الخاصة بها.

ولدى بايثون العديد من الطرق للتعامل مع السلاسل النصية ومعالجتها، ولعل الطريقة الأقدم هي استخدام محدد التحويل s% لتحديد المواضع المراد تضمينها ضمن السلسلة الرئيسية كجزء منها، أما الطريقة الأحدث والتي غدت موجودة اعتبارًا من الأصدار 3.6 فهي استخدام السلاسل النصية التنسيقية f-strings، وتُستخدم من خلال البادئة f قبل السلسلة النصية المراد صياغتها وبحصر الأجزاء المراد تضمينها في السلسلة ضمن أقواس معقوصة.

أما الصيغة [:] المستخدمة لإنشاء نسخ ضحلة عن القوائم قد غدت قديمة نسبيَا وقد لا تعد طريقة بايثونية، إلا أنها قد غدت طريقة شائعة لإنشاء النسخ الضحلة بسرعة.

ترجمة -وبتصرف- لجزء من الفصل السادس "كتابة شيفرات بايثون" من كتاب 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.


×
×
  • أضف...