التعليقات التوضيحية من النوع الجديد في Python 3.8 (البروتوكول ، النهائي ، TypedDict ، الحرفي)

خرج Python 3.8 الليلة وحصلت التعليقات التوضيحية على ميزات جديدة:


  • بروتوكولات
  • قواميس مكتوبة
  • المحدد النهائي
  • مطابقة القيمة الثابتة

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


بروتوكولات


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


مصطلحات مثل "بروتوكول التكرار" أو "بروتوكول الواصف" مألوفة بالفعل وقد استخدمت لفترة طويلة.
الآن يمكنك وصف البروتوكولات في شكل رمز والتحقق من امتثالها في مرحلة التحليل الثابت.


تجدر الإشارة إلى أنه بدءًا من Python 3.6 ، تتضمن وحدة الكتابة بالفعل العديد من البروتوكولات القياسية.
على سبيل المثال ، SupportsInt (الذي يتطلب الأسلوب __int__ ) ، SupportsBytes (يتطلب __bytes__ ) ، وبعض الآخرين.


وصف البروتوكول


يوصف البروتوكول كفئة عادية وراثة من البروتوكول. يمكن أن يكون لها طرق (بما في ذلك تلك مع التنفيذ) والحقول.
يمكن أن ترث الطبقات الحقيقية التي تنفذ البروتوكول ، ولكن هذا ليس ضروريًا.


 from abc import abstractmethod from typing import Protocol, Iterable class SupportsRoar(Protocol): @abstractmethod def roar(self) -> None: raise NotImplementedError class Lion(SupportsRoar): def roar(self) -> None: print("roar") class Tiger: def roar(self) -> None: print("roar") class Cat: def meow(self) -> None: print("meow") def roar_all(bigcats: Iterable[SupportsRoar]) -> None: for t in bigcats: t.roar() roar_all([Lion(), Tiger()]) # ok roar_all([Cat()]) # error: List item 0 has incompatible type "Cat"; expected "SupportsRoar" 

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


 class BigCatProtocol(SupportsRoar, Protocol): def purr(self) -> None: print("purr") 

الوراثة ، المكتوبة ذاتيا ، واستدعاء


يمكن أن تكون البروتوكولات ، مثلها مثل الفصول العادية ، من الأدوية الجنيسة بدلاً من تحديد Protocol و Generic[T, S,...] كأبوين Generic[T, S,...] يمكنك ببساطة تحديد Protocol[T, S,...]


هناك نوع آخر مهم من البروتوكول مكتوب ذاتيًا (انظر PEP 484 ). على سبيل المثال


 C = TypeVar('C', bound='Copyable') class Copyable(Protocol): def copy(self: C) -> C: class One: def copy(self) -> 'One': ... 

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


الشيكات وقت التشغيل


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


ومع ذلك ، تحتوي هذه الميزة على عدد من قيود الاستخدام. على وجه الخصوص ، الأدوية غير مدعومة


قواميس مكتوبة


عادة ما تستخدم الفئات (على وجه الخصوص ، فئات البيانات ) أو tuples المسماة لتمثيل البيانات المنظمة.
لكن في بعض الأحيان ، على سبيل المثال ، في حالة وصف بنية json ، قد يكون من المفيد وجود قاموس به مفاتيح معينة.
يقدم PEP 589 مفهوم TypedDict ، والذي كان متاحًا مسبقًا في امتدادات من mypy


مثل dataclasses أو tuples المكتوبة ، هناك طريقتان لإعلان قاموس مكتوب. بالميراث أو باستخدام المصنع:


 class Book(TypedDict): title: str author: str AlsoBook = TypedDict("AlsoBook", {"title": str, "author": str}) # same as Book book: Book = {"title": "Fareneheit 481", "author": "Bradbury"} # ok other_book: Book = {"title": "Highway to Hell", "artist": "AC/DC"} # error: Extra key 'artist' for TypedDict "Book" another_book: Book = {"title": "Fareneheit 481"} # error: Key 'author' missing for TypedDict "Book" 

القواميس المكتوبة تدعم الميراث:


 class BookWithDesc(Book): desc: str 

تبعًا للإعدادات الافتراضية ، تكون جميع مفاتيح القاموس مطلوبة ، ولكن يمكنك تعطيل ذلك بتمرير total=False عند إنشاء الفصل.
هذا ينطبق فقط على المفاتيح الموضحة في شباك التذاكر الحالي ولا يؤثر على الموروثة


 class SimpleBook(TypedDict, total=False): title: str author: str simple_book: SimpleBook = {"title": "Fareneheit 481"} # ok 

باستخدام TypedDict يحتوي على عدد من القيود. على وجه الخصوص:


  • الشيكات في وقت التشغيل من خلال isinstance غير معتمدة
  • يجب أن تكون المفاتيح القيم الحرفية أو القيم النهائية

بالإضافة إلى ذلك ، يحظر مثل هذا القاموس عمليات غير آمنة مثل .clear أو del .
يمكن أيضًا حظر العمل على مفتاح غير حرفي ، لأنه في هذه الحالة يستحيل تحديد نوع القيمة المتوقع


المعدل النهائي


يقدم PEP 591 المعدل النهائي (كديكور وتعليقات توضيحية) لعدة أغراض


  • تسمية فئة يستحيل أن ترث منها:

 from typing import final @final class Childfree: ... class Baby(Childfree): # error: Cannot inherit from final class "Childfree" ... 

  • تحديد الطريقة المحظورة تجاوزها:

 from typing import final class Base: @final def foo(self) -> None: ... class Derived(Base): def foo(self) -> None: # error: Cannot override final attribute "foo" (previously declared in base class "Base") ... 

  • تعيين متغير (معلمة دالة ، حقل فئة) ، ممنوع إعادة التعيين.

 ID: Final[float] = 1 ID = 2 # error: Cannot assign to final name "ID" SOME_STR: Final = "Hello" SOME_STR = "oops" # error: Cannot assign to final name "SOME_STR" letters: Final = ['a', 'b'] letters.append('c') # ok class ImmutablePoint: x: Final[int] y: Final[int] # error: Final name must be initialized with a value def __init__(self) -> None: self.x = 1 # ok ImmutablePoint().x = 2 # error: Cannot assign to final attribute "x" 

في هذه الحالة ، self.id: Final = 123 للنموذج self.id: Final = 123 ، ولكن فقط في طريقة __init__


حرفي


Literal النوع المحدد في PEP 586 عندما تحتاج إلى التحقق حرفيًا من قيم محددة


على سبيل المثال ، يشير Literal[42] أنه من المتوقع فقط 42 كقيمة.
من المهم عدم التحقق من تكافؤ القيمة فحسب ، بل أيضًا تحديد نوعها (على سبيل المثال ، لن يكون من الممكن استخدام False إذا كان 0 متوقعًا).


 def give_me_five(x: Literal[5]) -> None: pass give_me_five(5) # ok give_me_five(5.0) # error: Argument 1 to "give_me_five" has incompatible type "float"; expected "Literal[5]" give_me_five(42) # error: Argument 1 to "give_me_five" has incompatible type "Literal[42]"; expected "Literal[5]" 

في هذه الحالة ، يمكن إرسال عدة قيم بين قوسين ، وهو ما يعادل استخدام الاتحاد (قد لا تتزامن أنواع القيم).


لا يمكن استخدام التعبيرات (على سبيل المثال ، Literal[1+2] ) أو قيم الأنواع القابلة للتغيير.


كمثال مفيد ، استخدام Literal هي الدالة open() ، والتي تتوقع قيم mode معين.


اكتب التعامل في وقت التشغيل


إذا كنت تريد معالجة أنواع مختلفة من المعلومات ( مثلي ) أثناء تشغيل البرنامج ،
تتوفر الآن وظائف get_origin و get_args.


لذلك ، بالنسبة لنوع النموذج X[Y, Z,...] سيتم إرجاع النوع X كأصل ، و (Y, Z, ...) كوسائط
تجدر الإشارة إلى أنه إذا كانت X اسمًا مستعارًا للنوع المضمن أو النوع من وحدة collections ، فسيتم استبداله بالأصل.


 assert get_origin(Dict[str, int]) is dict assert get_args(Dict[int, str]) == (int, str) assert get_origin(Union[int, str]) is Union assert get_args(Union[int, str]) == (int, str) 

لسوء الحظ ، لم وظيفة __parameters__


مراجع


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


All Articles