إذا سألت مبرمجي بايثون عن أكثر ما يعجبهم في بايثون، فغالبًا ما يشيرون إلى سهولة قراءتها. في الواقع، تُعَد سهولة القراءة ركنًا اساسيًا في تصميم لغة بايثون، وذلك انطلاقًا من الحقيقة المعترف بها بأن الكود يُقرأ أكثر بكثير مما يُكتب.
أحد أسباب سهولة قراءة كود بايثون هو وجود مجموعة من الإرشادات الخاصة بأسلوب كتابة الكود Code Style، وما يُعرف بـ البايثونية Pythonic، أو العبارات البرمجية المثالية؛ بحيث عندما يشار إلى مبرمج بايثون مخضرم مثلًا، يُطلق عليه Pythonista؛ وعندما يشار إلى أجزاء من الكود بأنها ليست "بايثونية"، فإنه عادةً ما يعني أن هذه الأسطر من الكود لا تتبع الإرشادات المتعارف عليها وتفشل في التعبير عن الغرض منها بالطريقة التي تُعتبر الأفضل، أي الأكثر قابليةً للقراءة.
في بعض الحالات المحدودة لا يوجد اتفاق على أفضل طريقة للتعبير عن غرض معين في كود بايثون، ولكن هذه الحالات نادرة؛ مع ذلك سنشرح في هذا المقال كيفية إضافة كود بايثون جيد، وسهل القراءة وأنيق في نفس الوقت؛ وسنبدأ مع الإرشادات العامة الآتية:
الإرشادات العامة لكتابة كود أنيق باستخدام لغة بايثون
في ما يلي الإرشادات الأساسية لكتابة كود سهل القراءة وأنيق باستخدام لغة بايثون.
كتابة كود واضح وصريح
رغم أنه يمكن تنفيذ العديد من الحيل المعقدة باستخدام بايثون، إلا أنه يُفضل دائمًا أن يكون الكود واضحًا ومباشرًا، وفي الآتي مثال عن كيفية كتابة كود واضح وصريح:
- مثال سئ:
def make_complex(*args): x, y = args return dict(**locals())
- مثال جيد:
def make_complex(x, y): return {'x': x, 'y': y}
في الكود الجيد أعلاه، يتم استقبال القيم x
و y
مباشرةً من الشخص الذي يستدعي الدالة، ويتم إرجاع قاموس صريح. يمكن للمطور الذي يستخدم هذه الدالة أن يفهم تمامًا ما يجب فعله بمجرد قراءة السطر الأول والأخير؛ على عكس المثال السيء الذي يكون أقل وضوحًا.
عبارة واحدة في كل سطر
بينما يُسمح ببعض العبارات المركبة مثل قوائم الاستيعاب List Comprehensions التي تُقدّر لاختصارها وقدرتها التعبيرية، إلا أن وضع عبارتين غير مرتبطتين في نفس السطر من الكود يُعَد واحدًا من الممارسات السيئة في كتابة كود بلغة بايثون.
- مثال سئ:
print('one'); print('two') if x == 1: print('one') if <complex comparison> and <other complex comparison>: # do something
- مثال جيد:
print('one') print('two') if x == 1: print('one') cond1 = <complex comparison> cond2 = <other complex comparison> if cond1 and cond2: # do something
وسائط الدوال Function Arguments
يمكن تمرير الوسائط إلى الدوال عبر أربع طرق مختلفة، يمكن توضيحها في ما يلي:
1. الوسائط التموضعية Positional Arguments
تُعَد هذه الوسائط من أبسط أنواع الوسائط، وهي إلزامية وليس لها قيم افتراضية. تُستخدم هذه الوسائط في العادة عندما تكون الوسائط جزءًا أساسيًا من معنى الدالة ويكون ترتيبها طبيعيًا؛ فعلى سبيل المثال، في الدالة send(message, recipient)
أو point(x, y)
، لن يجد المستخدم صعوبةً في تذكر أن هذه الدوال تتطلب وسيطين وبترتيب محدد. وفي هذه الحالات، يمكن استخدام أسماء الوسائط عند استدعاء الدالة، مما يسمح بتغيير ترتيبها؛ فيمكن مثلًا كتابة:
send(recipient='World', message='Hello')
أو
point(y=2, x=1)
ومع ذلك، هذا يقلل من قابلية القراءة ويجعل الكود أطول دون داعٍ مقارنةً مع الاستدعاء المباشر مثل:
send('Hello', 'World')
و
point(1, 2)
2. الوسائط المعرفة بالأسماء Keyword Arguments
هذه الوسائط ليست إلزامية ولها قيم افتراضية، وتُستخدم عادةً للمدخلات الاختيارية التي تُمرر إلى الدالة. عندما تحتوي الدالة على أكثر من وسيطتين أو ثلاث وسائط تموضعية، يصبح تذكر ترتيبها أكثر صعوبة، وبالتالي فإن استخدام وسائط الكلمات المفتاحية ذات القيم الافتراضية يكون مفيدًا. على سبيل المثال، يمكن تعريف دالة send بطريقة أكثر اكتمالًا على النحو:
send(message, to, cc= None, bcc= None)
وهنا تكون كل من cc
و bcc
وسائط اختيارية، وستأخذ القيمة None
إذا لم يتم تمرير أي قيمة لها. يمكن استدعاء الدالة باستخدام وسائط الكلمات المفتاحية بعدة طرق في بايثون، حيث يمكن مثلًا اتباع ترتيب الوسائط المحدد في تعريف الدالة دون الذكر الصريح لأسماء الوسائط، مثل:
send('Hello', 'World', 'Family', 'Dad')
حيث يتم إرسال نسخة مخفية إلى Dad. من ناحية أخرى، يمكن أيضًا تغيير ترتيب الوسائط باستخدام أسمائها، مثل
send('Hello again', 'World', bcc='Dad', cc='Family')
ومع ذلك يُفضل تجنب هذه الطرق دون سبب قوي والالتزام بالتركيبة الأقرب إلى تعريف الدالة، مثل
send('Hello', 'World', cc='Family', bcc='Dad')
3. قائمة الوسائط العشوائية Arbitrary Argument List
هي الطريقة الثالثة لتمرير الوسائط إلى الدالة. إذا كان من الأفضل التعبير عن الدالة بتوقيع يحتوي على عدد قابل للتمديد من الوسائط الموقعية، فيمكن تعريفها باستخدام args*
، وستكون args
عبارة عن قائمة من كل الوسائط الموقعية المتبقية. على سبيل المثال، يمكن استدعاء send(message, *args)
مع كل مدخل كوسيطة:
send('Hello', 'Dad', 'Mom', 'Family')
وفي جسم الدالة ستكون args
تساوي ('Dad', 'Mom', 'Family')
، وهذا يتيح إمكانية تمرير عدد غير محدود من الوسائط الموقعية إلى الدالة، مما يجعلها مرنة للغاية في التعامل مع حالات الاستخدام المختلفة.
ملاحظة: تجدر الإشارة هنا إلى أنه يجب استخدام هذا النوع من التمرير بحذر، فإذا تلقت دالة ما قائمة من الوسائط ذات الطبيعة نفسها مثلًا، فيجب تعريفها كدالة لوسيطة واحدة، بحيث تكون تلك الوسيطة عبارة عن قائمة أو سلسلة. وهنا، إذا كان send يحتوي على عدة مستلمين، فمن الأفضل تعريفه بشكل صريح على النحو: send(message, recipients)
، واستدعاء الدالة الآتية:
send('Hello', [‘Dad, 'Mom', ‘Family])
4. الوسائط ذات الطول المتغير
هذه هي الطريقة الأخيرة لتمرير الوسائط إلى الدوال. إذا كانت الدالة تتطلب سلسلة غير محددة من الوسائط المسماة، فيمكن استخدام kwargs**
. وداخل الدالة، ستكون kwargs
عبارة عن قاموس يحتوي على جميع الوسائط المسماة التي لم يتم التقاطها بواسطة وسائط الكلمات المفتاحية الأخرى في تعريف الدالة.
وكما هو الحال مع تمرير قائمة الوسائط العشوائية، لابد من الحذر عند هذا النوع من التمرير أيضًا؛ إذ يجب استخدام هذه التقنيات القوية عندما تكون هناك ضرورة مثبتة لاستخدامها، ولا ينبغي استخدامها إذا كان البناء الأبسط والأكثر وضوحًا كافيًا للتعبير عن غرض الوظيفة؛ وهنا تأتي مسؤولية المبرمج في تحديد أي الوسائط تكون تموضعية وأيها تكون اختيارية باستخدام الكلمات المفتاحية، وكذلك اتخاذ قرار بشأن استخدام التقنيات المتقدمة لتمرير الوسائط. إذا تم اتباع النصائح أعلاه بحكمة، فسيكون من الممكن والممتع كتابة دوال بايثون التي تكون:
- سهلة القراءة: اسم الدالة أو وسائطها ليسوا بحاجة إلى شرح
- سهلة التعديل: حيث لا تؤدي إضافة وسيط جديد إلى كسر أجزاء أخرى من الكود
تجنب استخدام العصا السحرية
تُعَد بايثون أداةً قويةً، فهي تأتي مع مجموعة غنية من الأدوات والوظائف التي تسمح بتنفيذ العديد من الحيل المعقدة. على سبيل المثال، يمكننا القيام بما يلي:
- تغيير طريقة إنشاء وتكوين الكائنات
- تغيير طريقة استيراد المفسر للوحدات النمطية Modules
- وحتى تضمين روتينات لغة C داخل بايثون، وهو أمر موصى به إذا لزم الأمر
ومع ذلك، فإن كل هذه الخيارات لها العديد من العيوب، ومن الأفضل دائمًا استخدام الطريقة الأكثر مباشرة. العيب الرئيسي هو أن قابلية القراءة تتأثر كثيرًا عند استخدام هذه التركيبات؛ كما أن العديد من أدوات تحليل الكود، مثل pylint
أو pyflakes
، لن تكون قادرةً على فهم هذا الكود السحري.
يُفترض طبعًا أن يكون مطور بايثون على علم بهذه الإمكانيات اللامحدودة تقريبًا، لأنها تعزز الثقة بأنه لن تكون هناك مشكلة مستعصية في طريقه. ومع ذلك، فإن معرفة كيفية استخدام هذه الأدوات، وخاصةً متى لا يجب استخدامها، أمرًا في غاية الأهمية.
جميعنا مستخدمون مسؤولون
كما رأينا سابقًا، تسمح بايثون بالعديد من الحيل، وبعضها قد يكون خطيرًا. وكمثال جيد على ذلك أن أي كود عميل Client Code يمكنه تجاوز خصائص ووظائف الكائنات Objects، حيث لا توجد كلمة خاص Private في بايثون؛ وهذه الفلسفة تختلف تمامًا عن اللغات الدفاعية للغاية مثل جافا Java، التي توفر العديد من الآليات لمنع أي سوء الاستخدام.
تعبّر بايثون عن هذه الفلسفة بالمقولة الشهيرة:
اقتباس"نحن جميعًا مستخدمون مسؤولون"
وهذا لا يعني أنه لا توجد خصائص من نوع خاص Private، أو أنه لا يمكن تحقيق التغليف Encapsulation فيها كما يجب، بل أنه بدلًا من الاعتماد على جدران واقية يقيمها المطورون بين كودهم وكود الآخرين، فضّل مجتمع بايثون الاعتماد على مجموعة من الاتفاقيات التي تشير إلى أن هذه العناصر لا يجب الوصول إليها مباشرة.
الاتفاقية الرئيسية للخصائص ذات النمط الخاص وتفاصيل التنفيذ هي بإضافة شرطة سفلية _
كبادئة لجميع العناصر الداخلية؛ بحيث إذا قام كود العميل بكسر هذه القاعدة ووصل إلى هذه العناصر المحددة، فإن أي سلوك خاطئ أو مشاكل تحدث عند تعديل الكود تكون مسؤولية كود العميل. يتم تشجيع استخدام هذه الاتفاقية كثيرًا لضمان فصل أفضل للمسؤوليات وجعل تعديل الكود الحالي أسهل.
إرجاع القيم Returning Values
عندما تتعقد الدالة، من الشائع استخدام عدة عبارات return
داخل جسم الدالة. ومع ذلك، للحفاظ على وضوح الهدف ومستوى جيد من قابلية القراءة، يُفضل تجنب إرجاع قيم ذات معنى من نقاط متعددة داخل الدالة. هناك حالتان رئيسيتان لإرجاع القيم في الدالة:
- إرجاع النتيجة الطبيعية: عندما تتم معالجة الدالة طبيعيًا
- حالات الخطأ: عند الحالات التي تشير إلى وجود معاملات إدخال خاطئة أو أي سبب آخر يمنع الدالة من إكمال عملية الحساب أو المهمة المطلوبة
إذا كنا لا نرغب في استخدام الاستثناءات Exceptions للحالة الثانية، فقد نحتاج إلى إرجاع قيمة مثل None
أو False
للإشارة إلى أن الدالة لم تتمكن من العمل كما يجب. وفي هذه الحالة، من الأفضل إرجاع القيمة في أقرب وقت يُكتشف فيه السياق غير الصحيح، فهذا يساعد في تبسيط هيكل الدالة أكثر؛ حيث يمكن افتراض أن جميع الأكواد الموجودة بعد عبارة return
المرتبطة بالخطأ قد تم استيفاء الشروط اللازمة فيها لمواصلة حساب النتيجة الرئيسية للدالة، وهنا غالبًا ما يكون وجود عدة عبارات return
من هذا النوع ضروريًا.
مع ذلك، عندما تحتوي الدالة على عدة نقاط خروج رئيسية للمسار الطبيعي، يصبح تصحيح النتيجة المرجعة أكثر صعوبة، ولذلك قد يكون من الأفضل الحفاظ على نقطة خروج واحدة Single exit point، فهذا سيساعد في تحسين مسارات الكود Code Paths.
مثال:
def complex_function(a, b, c): if not a: return None # قد يكون رفع استثناء خيارًا أفضل if not b: return None # قد يكون رفع استثناء خيارًا أفضل # بعض الأكواد المعقدة لمحاولة حساب x من a و b و c # مقاومة الإغراء لإرجاع x إذا نجحت if not x: # بعض الحسابات البديلة لـ x return x # نقطة خروج واحدة للقيمة المرجعة x ستساعد في صيانة الكود
التعبيرات البرمجية Idioms
التعبير البرمجي Idiom هو ببساطة طريقة لكتابة الكود، تتم فيه مناقشة مفهوم التعبيرات البرمجية بشكل واسع على مواقع مثل c2 و Stack Overflow.
غالبًا ما يُشار إلى الكود البايثوني الذي يتبع التعبيرات البرمجية الشائعة على أنه بايثوني Pythonic. وعلى الرغم من وجود طريقة واحدة واضحة -ويفضل أن تكون واحدة فقط- لفعل الأشياء في بايثون، إلا أن طريقة كتابة الكود البايثوني بشكل صحيح قد لا تكون واضحة للمبتدئين، ولذلك يجب تعلم التعبيرات البرمجية الجيدة من البداية. وفيما يلي بعض التعبيرات البرمجية الشائعة في بايثون.
تفريغ القيم Unpacking
إذا كنت تعرف طول قائمة List أو مجموعة Tuple، فيمكنك تعيين أسماء لعناصرها باستخدام عملية التفريغ Unpacking. على سبيل المثال، بما أن الدالة ()enumerate
توفر مجموعة Tuple من عنصرين لكل عنصر في القائمة، فيمكنك فعل ما يلي:
for index, item in enumerate(some_list): # do something with index and item
يمكنك أيضًا استخدام هذه الطريقة لتبديل قيم المتغيرات:
a, b = b, a
كما أن التفريغ المتداخل Nested Unpacking يعمل أيضًا:
a, (b, c) = 1, (2, 3)
في بايثون 3، تم تقديم طريقة جديدة للتفريغ الموسع Extended Unpacking عبر PEP 3132:
a, *rest = [1, 2, 3] # a = 1, rest = [2, 3] a, *middle, c = [1, 2, 3, 4] # a = 1, middle = [2, 3], c = 4
إنشاء متغير غير مستخدم Ignored Variable
إذا كنا بحاجة إلى تعيين قيمة أثناء التفريغ Unpacking مثلَا ولكننا لن تحتاج إلى استخدام هذا المتغير، فيمكننا استخدام الشرطة السفلية المزدوجة __
:
filename = 'foobar.txt' basename, __, ext = filename.rpartition('.')
ملاحظة: توصي العديد من أدلة أسلوب كتابة كود بايثون باستخدام شرطة سفلية واحدة _
للمتغيرات غير المستخدمة Throwaway Variables بدلًا من الشرطة السفلية المزدوجة __
التي تم التوصية بها هنا، لكن المشكلة هي أن _
يشاع استخدامها كاسم مختصر لدالة ()gettext
، كما تُستخدم في الوضع التفاعلي Interactive Prompt لتخزين قيمة آخر عملية تم تنفيذها، لذا يُعَد استخدام الشرطة السفلية المزدوجة __
أكثر وضوحًا، وتقليلًا لخطر التداخل العرضي مع أي من هذه الاستخدامات الأخرى.
إنشاء قائمة بطول N تحتوي على نفس العنصر
من المهم هنا استخدام العامل *
الخاص بقوائم بايثون:
four_nones = [None] * 4
إنشاء قائمة بطول N تحتوي على قوائم متعددة
نظرًا لأن القوائم قابلة للتغيير Mutable، فإن العامل *
كما في المثال أعلاه، سيُنشئ قائمةً تحتوي على N
مرجعًا لنفس القائمة، وهو على الأرجح ليس ما نريده. لذا بدلًا من ذلك، يفضل استخدام List Comprehension كالتالي:
four_lists = [[] for __ in range(4)]
إنشاء سلسلة نصية String من قائمة
من التعبيرات الشائعة لإنشاء السلاسل النصية استخدام الدالة ()str.join
على سلسلة نصية فارغ:
letters = ['s', 'p', 'a', 'm'] word = ''.join(letters)
سيؤدي هذا إلى تعيين قيمة المتغير word
إلى spam
. يمكن تطبيق هذا التعبير على القوائم والمجموعات Tuples.
البحث عن عنصر في مجموعة
أحيانًا نحتاج إلى البحث خلال مجموعة من العناصر. دعونا ننظر إلى خياري القوائم Lists والمجموعات Sets:
s = set(['s', 'p', 'a', 'm']) l = ['s', 'p', 'a', 'm'] def lookup_set(s): return 's' in s def lookup_list(l): return 's' in l
على الرغم من أن الدالتين تبدوان متطابقتين، إلا أن دالة lookup_set
تستفيد من حقيقة أن المجموعات sets في بايثون هي جداول تجزئة Hashtables، مما يجعل أداء البحث بين الاثنين مختلفًا تمامًا.
لتحديد ما إذا كان عنصر ما موجودًا في قائمة، سيحتاج بايثون إلى المرور عبر كل عنصر حتى يعثر على العنصر المطابق، وهذا يستغرق وقتًا طويلًا، خاصةً للقوائم الطويلة.
من ناحية أخرى في المجموعات Sets، ستخبر التجزئة Hash بايثون بمكان البحث عن العنصر المطابق داخل المجموعة، ونتيجةً لذلك، يمكن إجراء البحث بسرعة، حتى إذا كانت المجموعة كبيرة. وللتنويه هنا، يعمل البحث في القواميس Dictionaries بنفس الطريقة.
بسبب هذه الاختلافات في الأداء، غالبًا ما يكون من الجيد استخدام المجموعات Sets، أو القواميس Dictionaries بدلًا من القوائم في الحالات التالية:
- عندما تحتوي المجموعة على عدد كبير من العناصر
- عندما نبحث باستمرار عن العناصر في المجموعة
- عندما لا تكون لدينا عناصر مكررة
بالنسبة للمجموعات الصغيرة أو المجموعات التي لن نكرر البحث فيها، فإن الوقت والذاكرة الإضافية المطلوبة لإعداد جدول التجزئة Hashtable غالبًا ما تكون أكبر من الوقت الذي يتم توفيره، وذلك بسبب سرعة البحث المحسنة.
فلسفة بايثون The Zen of Python
تُعرف أيضًا بـ PEP 20، وهي المبادئ التوجيهية لتصميم لغة بايثون. تتمثل فلسفة بايثون، بقلم تيم بيترز Tim Peters في ما يلي:
- الجميل أفضل من القبيح
- الواضح أفضل من الضمني
- البسيط أفضل من المعقد
- المعقد أفضل من الملتوي
- المسطح أفضل من المتداخل
- المتناثر أفضل من المكدس
- قابلية القراءة مهمة
- الحالات الخاصة ليست خاصة بما يكفي لكسر القواعد
- الأخطاء لا يجب أن تمر بصمت إلا إذا تم إسكاتها صراحةً
- في مواجهة الغموض، قاوم إغراء التخمين
- يجب أن تكون هناك طريقة واحدة واضحة للقيام بذلك، ويفضل أن تكون واحدة فقط
- قد لا تكون الطريقة الوحيدة الواضحة واضحة من البداية
- الآن أفضل من أبدًا
- إذا كان تنفيذ الفكرة صعب الشرح، فهي فكرة سيئة
- إذا كان تنفيذ الفكرة سهل الشرح، فقد تكون فكرة جيدة
- مساحات الأسماء Namespaces فكرة رائعة جدًا – فلنستخدمها أكثر!
دليل PEP 8
PEP 8 هو دليل أسلوب كتابة الكود الفعلي de facto للغة بايثون. تتوفر نسخة عالية الجودة وسهلة القراءة من PEP 8 على pep8.org.
يوصى جدًا بقراءة هذا الدليل، فمجتمع بايثون بأكمله يبذل قصارى جهده للالتزام بالإرشادات الواردة في هذا المستند. صحيح أنه قد تنحرف بعض المشاريع عنه أحيانًا، بينما قد تعدل مشاريع أخرى بعض التوصيات، مع ذلك فإن الالتزام به في كتابة كود بايثون يُعد فكرةً جيدةً عموًمًا، خاصةً وأنه يساعد في جعل الكود أكثر تناسقًا عند العمل على مشاريع مع مطورين آخرين.
هناك برنامج سطر أوامر يُسمى pycodestyle
(كان يُعرف سابقًا باسم pep8
) يمكنه التحقق من مدى توافق الكود مع PEP 8، ويمكنك تثبيته عن طريق تشغيل الأمر التالي في سطر الأوامر Terminal:
$ pip install pycodestyle
ثم شغله على ملف أو مجموعة ملفات للحصول على تقرير بأي انتهاكات:
$ pycodestyle optparse.py optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 optparse.py:222:34: W602 deprecated form of raising exception optparse.py:347:31: E211 whitespace before '(' optparse.py:357:17: E201 whitespace after '{' optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in'
التنسيق التلقائي Auto-Formatting
توجد عدة أدوات للتنسيق التلقائي تستطيع إعادة تنسيق الكود ليتوافق مع فلسفة PEP 8، يمكن ذكرها في الآتي:
منسق autopep8
يمكن استخدام برنامج autopep8 لإعادة تنسيق الكود تلقائيًا وفقًا لأسلوب PEP 8. قم بتثبيت البرنامج باستخدام:
$ pip install autopep8
نستخدمه لتنسيق الملف مباشرةً، وذلك باستخدام:
$ autopep8 --in-place optparse.py
استبعاد العلامة in-place--
سيؤدي إلى عرض الكود المعدل مباشرةً في وحدة التحكم للمراجعة. ستُجري العلامة aggressive--
تغييرات أكثر جوهرية ويمكن تطبيقها عدة مرات لتحقيق تأثير أكبر.
منسق yapf
بينما يركز autopep8 على حل انتهاكات PEP 8، يحاول yapf تحسين تنسيق الكود بجانب الالتزام بـ PEP 8. يهدف هذا المُنسق إلى تقديم كود يبدو جيدًا كما لو كان مكتوبًا بواسطة مبرمج يلتزم بـ PEP 8. يتم تثبيته باستخدام الأمر الآتي:
$ pip install yapf
يمكننا تشغيل التنسيق التلقائي لملف باستخدام الأمر:
$ yapf --in-place optparse.py
سيؤدي تشغيل الأمر بدون العلامة in-place--
إلى عرض الاختلافات diff للمراجعة قبل تطبيق التغييرات.
منسق black
يقدم المُنسق التلقائي black تنسيق حاسم ومحدد لقاعدة الكود، ويركز على توفير أسلوب موحد للكود دون الحاجة إلى التكوين من قبل المستخدمين، وبالتالي يمكن لمستخدمي black نسيان التنسيق تمامًا. بالإضافة إلى ذلك، وبسبب نهجه الحاسم، يضمن منسق black حدوث تغييرات بسيطة وذات صلة فقط في git diff. يمكن تثبيت المنسق عبر الأمر التالي:
$ pip install black
ويمكن تنسيق ملف بايثون باستخدام الأمر:
$ black optparse.py
توفر إضافة العلامة diff--
تعديلات الكود للمراجعة دون التطبيق المباشر.
الاتفاقيات Conventions
فيما يلي بعض الاتفاقيات التي يجب اتباعها لجعل الكود أسهل في القراءة.
التحقق من تساوي متغير مع ثابت
لا داعي لمقارنة True
أو None
أو 0
بشكل صريح، بل يمكننا ببساطة إضافتها إلى عبارة if
، كما هو موضح في قسم اختبار القيمة الحقيقية Truth Value Testing.
- مثال سيء:
if attr == True: print('True!') if attr == None: print('attr is None!')
- مثال جيد:
# تحقق من القيمة فقط if attr: print('attr is truthy!') # أو تحقق من العكس if not attr: print('attr is falsey!') # أو، بما أن None يعتبر False، تحقق منه بشكل صريح if attr is None: print('attr is None!')
الوصول إلى عنصر في القاموس
يفترض أن لا نستخدم الدالة ()dict.has_key
. بل بدلًا من ذلك، يمكننا استخدام الصيغة x in d
، أوتمرير قيمة افتراضية إلى ()dict.get
.
- مثال سيء:
d = {'hello': 'world'} if d.has_key('hello'): print(d['hello']) # prints 'world' else: print('default_value')
- مثال جيد:
d = {'hello': 'world'} print(d.get('hello', 'default_value')) # تكتب 'world' print(d.get('thingy', 'default_value')) # تكتب 'default_value' # :أو if 'hello' in d: print(d['hello'])
طرق مختصرة للتعامل مع القوائم
توفر قوائم الاستيعاب List Comprehensions طريقةً قويةً وموجزةً للعمل مع القوائم. تتبع تعابير المولدات Generator Expressions نفس صيغة قوائم الاستيعاب تقريبًا، ولكنها تُرجع مولدًا generator بدلًا من قائمة.
يتطلب إنشاء قائمة جديدة المزيد من العمل ويستخدم ذاكرة أكثر، لذا إذا كنا سنكتفي بالتكرار فقط خلال القائمة الجديدة، فمن الأفضل استخدام مُكرر iterator بدلًا من ذلك.
- مثال سيء:
# يخصص قائمة بجميع القيم (gpa, name) في الذاكرة دون داعٍ valedictorian = max([(student.gpa, student.name) for student in graduates])
- مثال جيد:
valedictorian = max((student.gpa, student.name) for student in graduates)
يمكننا استخدام قوائم الاستيعاب عندما نحتاج إلى إنشاء قائمة ثانية. على سبيل المثال، إذا كنا بحاجة إلى استخدام النتيجة عدة مرات؛ فإذا كان منطقنا معقدًا جدًا بالنسبة لقائمة استيعاب قصيرة أو تعبير مولد، فلا بد من التفكير في استخدام دالة مولد Generator Function بدلًا من إرجاع قائمة.
- مثال جيد:
def make_batches(items, batch_size): """ >>> list(make_batches([1, 2, 3, 4, 5], batch_size=3)) [[1, 2, 3], [4, 5]] """ current_batch = [] for item in items: current_batch.append(item) if len(current_batch) == batch_size: yield current_batch current_batch = [] yield current_batch
ملاحظة: لا نستخدم قوائم الاستيعاب بسبب تأثيراتها الجانبية
- مثال سيء:
[print(x) for x in sequence]
- مثال جيد:
for x in sequence: print(x)
تصفية قائمة Filtering a List
- مثال سيء: إزالة عناصر من القائمة عند وجود التكرار.
# تصفية العناصر الأكبر من 4 a = [3, 4, 5] for i in a: if i > 4: a.remove(i)
عمل عدة تمريرات على القائمة. y
while i in a: a.remove(i)
- مثال جيد: يفضل استخدام قوائم الاستيعاب List Comprehensions أو تعابير المولدات Generator Expressions.
# قوائم الاستيعاب تنشئ كائن قائمة جديد filtered_values = [value for value in sequence if value != x] # تعابير المولدات لا تنشئ قائمة أخرى filtered_values = (value for value in sequence if value != x)
الآثار الجانبية المحتملة لتعديل القائمة الأصلية
يمكن أن يكون تعديل القائمة الأصلية محفوفًا بالمخاطر إذا كانت هناك متغيرات أخرى تشير إليها. ولكن يمكننا استخدام تعيين الشرائح Slice Assignment إذا كنا نرغب في ذلك.
# استبدال محتويات القائمة الأصلية sequence[::] = [value for value in sequence if value != x]
تعديل القيم في قائمة
- مثال سيء: لا بد من تذكر أن عملية التعيين لا تنشئ كائنًا جديدًا. إذا كان هناك متغيران أو أكثر يشيران إلى نفس القائمة، فإن تغيير أحدها سيؤثر على جميعها.
# إضافة 3 إلى جميع عناصر القائمة. a = [3, 4, 5] b = a # a و b يشيران إلى نفس كائن القائمة for i in range(len(a)): a[i] += 3 # b[i] تتغير أيضًا
- مثال جيد: من الأكثر أمانًا إنشاء كائن قائمة جديد وترك القائمة الأصلية دون تغيير.
a = [3, 4, 5] b = a # تعيين المتغير "a" إلى قائمة جديدة دون تغيير "b" a = [i + 3 for i in a]
من المهم استخدام الدالة ()enumerate
لتتبع موقعنا في القائمة.
a = [3, 4, 5] for i, item in enumerate(a): print(i, item) # الناتج: # 0 3 # 1 4 # 2 5
تتميز الدالة ()enumerate
بسهولة قراءة أفضل من التعامل مع عداد يدويًا. بالإضافة إلى ذلك، فهي مُحسنة أكثر للتعامل مع المُكررات iterators.
القراءة من ملف
يمكننا استخدام بناء الجملة with open
للقراءة من الملفات، وسيؤدي هذا إلى إغلاق الملف تلقائيًا.
- مثال سيء:
f = open('file.txt') a = f.read() print(a) f.close()
- مثال جيد:
with open('file.txt') as f: for line in f: print(line)
تُعَد العبارة with
أفضل لأنها تضمن إغلاق الملف دائمًا، حتى إذا تم رفع استثناء exception داخل الكتلة الخاصة بـ with
.
طول الأسطر
عندما يكون سطر الكود أطول من الحد المقبول، يجب تقسيمه إلى عدة أسطر فعلية. سيدمج مفسر بايثون الأسطر المتتالية إذا كان الحرف الأخير من السطر هو شرطة مائلة للخلف \
. يُعَد هذا مفيدًا في بعض الحالات، ولكن يجب تجنبه أحيانًا؛ إذ يمكن أن تكسر عمليية إضافة مسافة بيضاء في نهاية السطر بعد الشرطة المائلة للخلف \
الكود وقد يؤدي هذا إلى نتائج غير متوقعة، ،الحل الأفضل هو استخدام الأقواس حول العناصر.
عند ترك قوس غير مغلق في نهاية السطر، سوف يدمج مفسر بايثون السطر الموالي إلى غاية إغلاق الأقواس. وينطبق نفس السلوك على الأقواس المعقوفة {}
والأقواس المربعة []
.
- مثال سيء:
my_very_big_string = """For a long time I used to go to bed early. Sometimes, \ when I had put out my candle, my eyes would close so quickly that I had not even \ time to say “I’m going to sleep.”""" from some.deep.module.inside.a.module import a_nice_function, another_nice_function, \ yet_another_nice_function
- مثال جيد:
my_very_big_string = ( "For a long time I used to go to bed early. Sometimes, " "when I had put out my candle, my eyes would close so quickly " "that I had not even time to say “I’m going to sleep.”" ) from some.deep.module.inside.a.module import ( a_nice_function, another_nice_function, yet_another_nice_function)
ومع ذلك، في كثير من الأحيان، تكون الحاجة إلى تقسيم سطر منطقي طويل علامة على محاولة القيام بعدة الأشياء في نفس الوقت، مما قد يعيق قابلية القراءة.
ترجمة -وبتصرّف- للمقال Code Style.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.