إذا سألنا مبرمجي بايثون عن أكثر ما يعجبهم في هذه اللغة، فغالبًا ما سيشيرون إلى سهولة قراءتها. أحد أسباب هذه السهولة هو وجود مجموعة من الإرشادات الخاصة بأسلوب كتابة الكود Code Style وما يُعرف بالبايثونية Pythonic، أو العبارات البرمجية المثالية.
مع ذلك هناك بعض الحالات التي لا يكون فيها اتفاق حول أفضل طريقة للتعبير عن غرض معين في كود بايثون، ولكن هذه الحالات نادرة؛ مع ذلك سنشرح في هذا المقال كيفية إضافة كود بايثون جيد، وسهل القراءة وأنيق في نفس الوقت.
كتابة كود واضح وصريح
رغم أنه يمكن تنفيذ العديد من الحيل المعقدة باستخدام بايثون، إلا أنه يُفضل دائمًا أن يكون الكود واضحًا ومباشرًا، وفي الآتي مثال عن كيفية كتابة كود واضح وصريح:
# مثال سيء 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
تُعَد هذه الوسائط الموضعية من أبسط أنواع الوسائط، وهي إلزامية وليس لها قيم افتراضية. تُستخدم هذه الوسائط في العادة عندما تكون الوسائط جزءًا أساسيًا من معنى الدالة ويكون ترتيبها طبيعيًا، أو تحتوي على وسيطين فقط يسهل تذكرهما؛ فعلى سبيل المثال، لن تكون هناك صعوبة في تذكر أو تحديد ترتيب الدالة point(x, y)
، لذا يمكن استخدام أسماء وسائطها مباشرة عند استدعائها دون الحاجة للتأكيد على مكان ترتيبها. ورغم أنه بإمكاننا كتابتها على هذا النحو مثلًا: point(y=2, x=1)
، إلا أن هذا يقلل من قابلية القراءة ويجعل الكود أطول دون داعٍ مقارنةً مع الاستدعاء المباشر على النحو point(x, y)
.
2. تمرير الوسائط المعرفة بالأسماء Keyword Arguments
هذه الوسائط ليست إلزامية ولها قيم افتراضية، وتُستخدم عادةً للمدخلات الاختيارية التي تُمرر إلى الدالة.
عندما تحتوي الدالة على أكثر من الوسائط الموضوعية، يصبح تذكر ترتيبها أكثر صعوبة، وهنا سيكون من المفيد استخدام الوسائط المعرفة بالأسماء ذات القيم الافتراضية. على سبيل المثال، يمكن تعريف دالة send بطريقة مكتملة، أو بذكر صريح، أو دونه، مع ذلك لا يكون من المناسب اتباع أي منما سبق لكتابة كود أنيق، بل ويُفضل تجنبها حتى في حال عدم الحاجة إليها، مع ضرورة الالتزام بالتركيبة الأقرب إلى تعريف الدالة. وفي الآتي توضيح لخيارات التمرير المذكورة:
# تعريف الدالة بطريقة مكتملة send(message, to, cc= None, bcc= None) # تعريف الدالة دون الذكر الصريح لأسماء الوسائط send('Hello', 'World', 'Family', 'Dad') # تغيير ترتيب الوسائط باستخدام أسمائها send('Hello again', 'World', bcc='Dad', cc='Family') # الخيار الأفضل من كل ما سبق # الالتزام بالتركيبة الأقرب للدالة send('Hello', 'World', cc='Family', bcc='Dad')
3. تمرير قائمة الوسائط العشوائية Arbitrary Argument List
إذا كان من الأفضل التعبير عن الدالة بطريقة تحتوي على وسائط موضعية Positional Arguments بعدد قابل للزيادة والتوسعة، فيمكن تعريفها باستخدام args*
، وستكون args
هنا عبارة عن قائمة من كل الوسائط الموضعية المتبقية. يمكن مثلًا استدعاء send(message, *args)
مع كل مدخل كوسيطة على النحو الآتي:
# استدعاء الدالة مع كل مدخل كوسيطة send('Hello', 'Dad', 'Mom', 'Family')
وفي جسم الدالة ستكون args
تساوي ('Dad', 'Mom', 'Family')
، وهذا يتيح إمكانية تمرير عدد غير محدود من الوسائط الموضعية إلى الدالة، مما يجعلها مرنةً للغاية في التعامل مع حالات الاستخدام المختلفة.
مع ذلك، تجدر الإشارة هنا إلى أنه يجب استخدام هذا النوع من التمرير بحذر، فإذا تلقت الدالة قائمة من الوسائط ذات نفس الطبيعة مثلًا، فيجب تعريفها كدالة لوسيطة واحدة، بحيث تكون تلك الوسيطة عبارة عن قائمة أو سلسلة. وهنا، إذا كان send يحتوي على عدة مستلمين، فمن الأفضل تعريفه بشكل صريح واستدعاء الدالة كما يلي:
# التعريف الصريح لـ send send(message, recipients) # استدعاء الدالة send('Hello', [‘Dad, 'Mom', ‘Family])
4. تمرير قاموس الوسائط العشوائية المعرفة بأسماء Arbitrary Keyword Argument Dictionary
هذه هي الطريقة الأخيرة لتمرير الوسائط إلى الدوال. إذا كانت الدالة تتطلب سلسلة غير محددة من الوسائط المسماة، فيمكن استخدام kwargs**
، وداخل الدالة ستكون kwargs
عبارة عن قاموس يحتوي على جميع الوسائط المسماة التي لم يتم التقاطها عبر باقي الوسائط المعرفة بأسماء في تعريف الدالة.
يمكن التعرف بالتفصيل على كيفية استخدام الوسيط kwargs
عبر مقال كيفية استخدام args* و kwargs** في بايثون.
وكما هو الحال مع تمرير قائمة الوسائط العشوائية، لابد من الحذر عند استخدام هذا النوع من التمرير أيضًا؛ إذ يجب استعماله عند الحاجة فقط، ولا ينبغي استخدامه إذا كان البناء الأبسط والأكثر وضوحًا كافيًا للتعبير عن غرض الوظيفة.
تجنب استخدام العصا السحرية
تُعَد بايثون لغة قويةً، فهي تأتي مع مجموعة غنية من الأدوات والوظائف التي تسمح بتنفيذ العديد من الحيل المعقدة، مثل تغيير طريقة إنشاء وتكوين الكائنات، وتغيير طريقة استيراد المفسر للوحدات النمطية Modules، وتضمين روتينات لغة C داخل بايثون؛ ومع ذلك، فإن كل هذه الخيارات لها العديد من العيوب، ومن الأفضل دائمًا استخدام الطريقة المباشرة أكثر، لأن قابلية القراءة تتأثر كثيرًا عند استخدام هذه التركيبات؛ كما أن العديد من أدوات تحليل الكود، مثل pylint
أو pyflakes
، لن تكون قادرةً على فهم هذا الكود السحري.
جميعنا مستخدمون مسؤولون
في لغة بايثون، أي كود عميل Client Code بإمكانه تجاوز خصائص ووظائف الكائنات Objects، إذ لا توجد ببايثون كلمة خاص Private؛ وهذه الفلسفة تختلف تمامًا عن اللغات الدفاعية للغاية لمنع أي إساءة في الاستخدام. مع ذلك، تعبّر بايثون عن هذه الفلسفة بالمقولة الشهيرة:
اقتباس"نحن جميعًا مستخدمون مسؤولون"
وهذا لا يعني أنه لا توجد خصائص من نوع خاص Private، أو أنه لا يمكن تحقيق التغليف Encapsulation فيها كما يجب، بل أنه بدلًا من الاعتماد على جدران يقيمها المطورون بين كودهم وكود الآخرين، فضّل مجتمع بايثون الاعتماد على مجموعة من الاتفاقيات التي تشير إلى أن هذه العناصر لا يجب الوصول إليها مباشرة.
الاتفاقية الرئيسية هنا هي بإضافة شرطة سفلية _
كبادئة لجميع العناصر الداخلية؛ بحيث إذا قام كود العميل بكسر هذه القاعدة ووصل إلى هذه العناصر المحددة، فإن أي سلوك خاطئ أو مشاكل تحدث عند تعديل الكود تكون من مسؤولية كود العميل.
إرجاع القيم Returning Values
عندما تتعقد الدالة يشاع استخدام عدة عبارات return
داخل جسمها، وهناك حالتان رئيسيتان لإرجاع القيم في الدالة:
- إرجاع النتيجة الطبيعية: عندما تتم معالجة الدالة طبيعيًا
- حالات الخطأ: عند الحالات التي تشير إلى وجود معاملات إدخال خاطئة أو أي سبب آخر يمنع الدالة من إكمال عملية الحساب أو المهمة المطلوبة
إذا كنا لا نرغب في استخدام الاستثناءات Exceptions للحالة الثانية، فقد نحتاج إلى إرجاع قيمة مثل None
أو False
للإشارة إلى أن الدالة لم تتمكن من العمل كما يجب. وفي هذه الحالة، من الأفضل إرجاع القيمة في أقرب وقت يُكتشف فيه السياق غير الصحيح، فهذا يساعد في تبسيط هيكل الدالة أكثر.
مع ذلك، عندما تحتوي الدالة على عدة نقاط خروج رئيسية للمسار الطبيعي، يصبح تصحيح النتيجة المرجعة أكثر صعوبة، ولذلك قد يكون من الأفضل الحفاظ على نقطة خروج واحدة Single exit point، فهذا سيساعد في تحسين مسارات الكود Code Paths.
التعبيرات البرمجية Idioms
غالبًا ما يُشار إلى الكود البايثوني الذي يتبع التعبيرات البرمجية الشائعة Idioms على أنه بايثوني Pythonic. وعلى الرغم من وجود طريقة واحدة واضحة في بايثون، إلا أن طريقة كتابة الكود البايثوني كما يجب قد لا تكون واضحة للمبتدئين، ولذلك يجب تعلم التعبيرات البرمجية الجيدة من البداية. وفيما يلي بعض التعبيرات البرمجية الشائعة في بايثون.
تفريغ القيم Unpacking
يمكننا تعيين أسماء عناصر طول قائمة List أو صف Tuple باستخدام عملية التفريغ Unpacking. على سبيل المثال، بما أن الدالة ()enumerate
توفر صف Tuple من عنصرين لكل عنصر في القائمة، فيمكننا تفريع القيم المحتواة بأكثر من طريقة، سواءً الطريقة العادية، أو طريقة تبديل قيم المتغيرات، أو حتى عن طريق ما يُعرف بالتفريغ المتداخل Nested Unpacking؛ لكن الطريقة الأحدث والأفضل، هي طريقة التفريغ الموسع Extended Unpacking، وجميعها موضحة في ما يلي:
# تفريغ القيم المحتواة على قائمة for index, item in enumerate(some_list): # do something with index and item # التفريغ لتبديل المتغيرات a, b = b, a # التفريغ المتداخل a, (b, c) = 1, (2, 3) # طريقة التفريغ الموسع 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
مرجعًا لنفس القائمة، وهو على الأرجح ليس ما نريده؛ لذا بدلًا من ذلك، يفضل استخدام قوائم الاستيعاب كالتالي:
# إنشاء قائمة بطول N تحتوي على قوائم متعددة four_lists = [[] for __ in range(4)]
إنشاء سلسلة نصية String من قائمة
من التعبيرات الشائعة لإنشاء السلاسل النصية استخدام الدالة ()str.join
على سلسلة نصية فارغة:
# إنشاء سلسلة نصية String من قائمة letters = ['s', 'p', 'a', 'm'] word = ''.join(letters)
سيؤدي هذا إلى تعيين قيمة المتغير word
إلى spam
. يمكن تطبيق هذا التعبير على القوائم والصفوف Tuples.
البحث عن عنصر في مجموعة Set
قد نحتاج في بعض الأحيان إلى البحث في مجموعة من العناصر. على سبيل المثال:
# البحث عن عنصر في مجموعة 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، مما يجعل أداء البحث بين الاثنين مختلفًا تمامًا.
لتحديد ما إذا كان عنصر ما موجودًا في قائمة، ستحتاج بايثون إلى المرور عبر كل عنصر حتى يعثر على العنصر المطابق، وهذا يستغرق وقتًا طويلًا، خاصةً للقوائم الطويلة. ومن ناحية أخرى، ستخبر التجزئة Hash بايثون بمكان البحث عن العنصر المطابق داخل المجموعة، ونتيجةً لذلك، يمكن إجراء البحث بسرعة، حتى إذا كانت المجموعة كبيرة.
بسبب هذه الاختلافات في الأداء، غالبًا ما يكون من الجيد استخدام المجموعات Sets، أو القواميس Dictionaries بدلًا من القوائم في الحالات التالية:
- عندما تحتوي المجموعة على عدد كبير من العناصر
- عندما نبحث باستمرار عن العناصر في المجموعة
- عندما لا تكون لدينا عناصر مكررة
وبالنسبة للمجموعات الصغيرة أو المجموعات التي لن نكرر البحث فيها، فإن الوقت والذاكرة الإضافية المطلوبة لإعداد جدول التجزئة Hashtable غالبًا ما تكون أكبر من الوقت الذي يتم توفيره، وذلك بسبب سرعة البحث المحسنة.
فلسفة بايثون The Zen of Python
تُعرف أيضًا بـ PEP 20، وهي المبادئ التوجيهية لتصميم لغة بايثون. تتمثل فلسفة بايثون، بقلم تيم بيترز Tim Peters في ما يلي:
- الجميل أفضل من القبيح
- الواضح أفضل من الضمني
- البسيط أفضل من المعقد
- المعقد أفضل من الملتوي
- المسطح أفضل من المتداخل
- المتناثر أفضل من المكدس
- قابلية القراءة مهمة
- الحالات الخاصة ليست خاصة بما يكفي لكسر القواعد
- الأخطاء لا يجب أن تمر بصمت إلا إذا تم إسكاتها صراحةً
- في مواجهة الغموض، قاوم إغراء التخمين
- يجب أن تكون هناك طريقة واحدة واضحة للقيام بالأمر، ويفضل أن تكون واحدة فقط
- قد لا تكون الطريقة الوحيدة الواضحة واضحة من البداية
- الآن أفضل من أبدًا
- إذا كان تنفيذ الفكرة صعب الشرح، فهي فكرة سيئة
- إذا كان تنفيذ الفكرة سهل الشرح، فقد تكون فكرة جيدة
- مساحات الأسماء Namespaces فكرة رائعة جدًا وعلينا استخدامها أكثر
دليل PEP 8
PEP 8 هو دليل أسلوب كتابة الكود الفعلي de facto للغة بايثون. يوصى جدًا بقراءة هذا الدليل، لأنه يساعد في جعل الكود أكثر تناسقًا عند العمل على مشاريع مع مطورين آخرين.
تتوفر عدة أدوات للتحقق مما إذا كان الكود متوافق مع أسلوب PEP 8، نذكرها في الآتي.
المنسق الآلي autopep8
يمكن استخدام برنامج autopep8 لإعادة تنسيق الكود تلقائيًا وفقًا لأسلوب PEP 8. ويمكننا تثبيت البرنامج باستخدام:
# تثبيت منسق autopep8 $ pip install autopep8
نستخدمه لتنسيق الملف مباشرةً، وذلك باستخدام:
# التنسيق المباشر للملف باستخدام autopep8 $ autopep8 --in-place optparse.py
حيث سيؤدي استبعاد العلامة in-place--
إلى عرض الكود المعدل مباشرةً في وحدة التحكم للمراجعة، وستُجري العلامة aggressive--
تغييرات أكثر جوهرية ويمكن تطبيقها عدة مرات لتحقيق تأثير أكبر.
المنسق الآلي yapf
بينما يركز autopep8 على حل انتهاكات PEP 8، يحاول yapf تحسين تنسيق الكود بجانب الالتزام بـ PEP 8. يهدف هذا المُنسق إلى تقديم كود يبدو جيدًا كما لو كان مكتوبًا بواسطة مبرمج يلتزم بـ PEP 8، ويُثبت باستخدام الأمر الآتي:
# تثبيت yapf $ pip install yapf
يمكننا تشغيل التنسيق الآلي لملف باستخدام الأمر:
# التشغيل التلقائي للملف عبر منسق yapf $ yapf --in-place optparse.py
سيؤدي تشغيل الأمر بدون العلامة in-place--
إلى عرض الاختلافات diff للمراجعة قبل تطبيق التغييرات.
المنسق الآلي black
يقدم المُنسق التلقائي black تنسيقًا حاسمًا ومحددًا لقاعدة الكود، ويركز على توفير أسلوب موحد للكود دون الحاجة إلى التكوين من قبل المستخدمين، وبالتالي يمكن لمستخدمي black نسيان التنسيق تمامًا. بالإضافة إلى ذلك، يضمن منسق black حدوث تغييرات بسيطة وذات صلة فقط في git diff؛ ويمكن تثبيت المنسق عبر الأمر التالي:
# تثبيت المنسق black $ pip install black
ويمكن تنسيق ملف بايثون باستخدام الأمر:
# تنسيق ملف بايثون عبر أمر black $ black optparse.py
توفر إضافة العلامة diff--
تعديلات الكود للمراجعة دون التطبيق المباشر.
الاتفاقيات Conventions
فيما يلي بعض الاتفاقيات التي يجب اتباعها لجعل الكود أسهل في القراءة.
التحقق من تساوي متغير مع ثابت
لا داعي لمقارنة True
أو None
أو 0
بشكل صريح، إذ يمكننا إضافتها إلى عبارة if
مباشرةً. في ما يلي مثال توضيحي عن الأمر:
# مثال سيء 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 بدلًا من إرجاع قائمة.
# استخدام دالة المولد 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
من أجل تصفية قائمة يُفضل استخدام قوائم الاستيعاب List Comprehensions أو تعابير المولدات Generator Expressions، بدل تصفيتها بطريقة مكررة وغير دقيقة.
# مثال سيء # إزالة عناصر من القائمة عند وجود التكرار # تصفية العناصر الأكبر من 4 a = [3, 4, 5] for i in a: if i > 4: a.remove(i) # عمل عدة تمريرات على القائمة while i in a: a.remove(i) # مثال جيد # قوائم الاستيعاب تنشئ كائن قائمة جديد 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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.