هذه هي المجموعة الثالثة من نصائح وبرمجة 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
تذكر أن
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.