Pythonetc أغسطس 2018



هذه هي المجموعة الثالثة من نصائح وبرمجة Python من موجزpythonetc الخاص بي.

التحديدات السابقة:



طريقة المصنع


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

في هذا المثال ، يقبل __init__ host port كوسيطتين لإنشاء اتصال قاعدة بيانات:

 class Query: def __init__(self, host, port): self._connection = Connection(host, port) 

خيار إعادة البيع:

 class Query: def __init__(self, connection): self._connection = connection @classmethod def create(cls, host, port): return cls(Connection(host, port)) 

يتمتع هذا النهج على الأقل بالمزايا التالية:

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

عظمى أو تالية


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

 class BaseTestCase(TestCase): def setUp(self): self._db = create_db() class UserTestCase(BaseTestCase): def setUp(self): super().setUp() self._user = create_user() 

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

 class Top: def foo(self): return 'top' class Left(Top): def foo(self): return super().foo() class Right(Top): def foo(self): return 'right' class Bottom(Left, Right): pass # prints 'right' print(Bottom().foo()) 

تذكر أن super() يمكن أن ينتج نتائج مختلفة اعتمادًا على المكان الذي تم استدعاء الطريقة منه في الأصل.

 >>> Bottom().foo() 'right' >>> Left().foo() 'top' 


مساحة اسم مخصصة لإنشاء فئة


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

 class Meta(type): def __new__(meta, name, bases, ns): print(ns) return super().__new__( meta, name, bases, ns ) class Foo(metaclass=Meta): B = 2 

يعرض هذا الرمز {'__module__': '__main__', '__qualname__':'Foo', 'B': 3} .

من الواضح ، إذا أدخلت شيئًا مثل B = 2; B = 3 B = 2; B = 3 ، ثم سيرى metaclass B = 3 فقط ، لأن هذه القيمة فقط في ns . ينبع هذا القيد من حقيقة أن metaclass يبدأ في العمل فقط بعد إعدام الجسم.

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

 class CustomNamespace(dict): def __setitem__(self, key, value): print(f'{key} -> {value}') return super().__setitem__(key, value) class Meta(type): def __new__(meta, name, bases, ns): return super().__new__( meta, name, bases, ns ) @classmethod def __prepare__(metacls, cls, bases): return CustomNamespace() class Foo(metaclass=Meta): B = 2 B = 3 

نتيجة تنفيذ التعليمات البرمجية:

 __module__ -> __main__ __qualname__ -> Foo B -> 2 B -> 3 

وبالتالي فإن enum.Enum محمي من الازدواجية .

ماتبلوتليب


matplotlib هي مكتبة بيثون معقدة ومرنة matplotlib . تدعمه العديد من المنتجات ، بما في ذلك Jupyter و Pycharm. فيما يلي مثال على عرض matplotlib بسيط باستخدام matplotlib : https://repl.it/@VadimPushtaev/myplotlib (انظر صورة عنوان هذا المنشور).

دعم المنطقة الزمنية


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

الأكثر شعبية منهم هو pytz . لكن الحقيقة هي أن pytz لا يتوافق تمامًا مع واجهة tzinfo . جاء ذلك في بداية توثيق pytz : "تختلف هذه المكتبة عن Python API الموثق لعمليات تنفيذ tzinfo."

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

 In : paris = pytz.timezone('Europe/Paris') In : str(datetime(2017, 1, 1, tzinfo=paris)) Out: '2017-01-01 00:00:00+00:09' 

لاحظ الإزاحة +00: 09. يجب استخدام Pytz مثل هذا:

 In : str(paris.localize(datetime(2017, 1, 1))) Out: '2017-01-01 00:00:00+01:00' 

بالإضافة إلى ذلك ، بعد أي عمليات حسابية ، تحتاج إلى تطبيق normalize على كائنات datetime لتجنب تغيير الإزاحة (على سبيل المثال ، على حدود فترة DST).

 In : new_time = time + timedelta(days=2) In : str(new_time) Out: '2018-03-27 00:00:00+01:00' In : str(paris.normalize(new_time)) Out: '2018-03-27 01:00:00+02:00' 

إذا كان لديك Python 3.6 ، توصي الوثائق باستخدام dateutil.tz بدلاً من pytz . هذه المكتبة متوافقة تمامًا مع tzinfo ، ويمكن تمريرها tzinfo ولا تحتاج إلى استخدام normalize . صحيح أنها تعمل بشكل أبطأ.

إذا كنت تريد أن تعرف لماذا لا يدعم pytz واجهة برمجة تطبيقات datetime ، أو تريد رؤية المزيد من الأمثلة ، فاقرأ هذه المقالة.

التوقف السحري


في كل مرة next(x) استدعاء next(x) ، تقوم بإرجاع قيمة جديدة من المكرر x حتى يتم طرح استثناء. إذا اتضح أنه StopIteration ، فإن StopIteration قد استنفد ولم يعد بإمكانه تقديم قيم. إذا كان المولد StopIteration تلقائيًا في نهاية الجسم StopIteration :

 >>> def one_two(): ... yield 1 ... yield 2 ... >>> i = one_two() >>> next(i) 1 >>> next(i) 2 >>> next(i) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 

يمكن معالجة StopIteration تلقائيًا بواسطة الأدوات التي تستدعي next :

 >>> list(one_two()) [1, 2] 

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

 def one_two(): yield 1 yield 2 def one_two_repeat(n): for _ in range(n): i = one_two() yield next(i) yield next(i) yield next(i) print(list(one_two_repeat(3))) 

هنا ، yield الأخير هو خطأ: StopIteration استثناء StopIteration تكرار list(...) . نحصل على النتيجة [1, 2] . ومع ذلك ، في Python 3.7 تغير هذا السلوك. StopIteration استبدال StopIteration Alien بـ RuntimeError :

 Traceback (most recent call last): File "test.py", line 10, in one_two_repeat yield next(i) StopIteration The above exception was the direct cause of the following exception: Traceback (most recent call last): File "test.py", line 12, in <module> print(list(one_two_repeat(3))) RuntimeError: generator raised StopIteration 

يمكنك استخدام __future__ import generator_stop لتمكين نفس السلوك منذ Python 3.5.

Source: https://habr.com/ru/post/ar422789/


All Articles