مقدمة

رسم توضيحي لـ Magdalena Tomczyk
الجزء الثاني
Python هي لغة تتميز بالكتابة الديناميكية وتسمح لنا بمعالجة متغيرات الأنواع المختلفة بحرية. ومع ذلك ، عند كتابة التعليمات البرمجية ، بطريقة أو بأخرى ، نفترض أنواع المتغيرات التي سيتم استخدامها (قد يكون ذلك بسبب تقييد الخوارزمية أو منطق العمل). وللتشغيل الصحيح للبرنامج ، من المهم بالنسبة لنا أن نجد أخطاء في أقرب وقت ممكن مرتبطة بنقل البيانات من النوع الخطأ.
مع الاحتفاظ بفكرة كتابة البط الديناميكي في الإصدارات الحديثة من Python (3.6+) ، فإنه يدعم التعليقات التوضيحية لأنواع المتغيرات ، وحقول الفئات ، والوسائط ، وقيم إرجاع الوظائف:
تتم قراءة التعليقات التوضيحية بالكتابة بواسطة مترجم Python ولم تعد قيد المعالجة ، ولكنها متاحة للاستخدام من خلال كود الطرف الثالث وهي مصممة في المقام الأول للاستخدام من قبل المحللون الاستاتيكيين.
اسمي أندريه تيخونوف وأنا منخرط في تطوير الواجهة الخلفية في لامودا.
في هذه المقالة ، أود شرح أساسيات استخدام التعليقات التوضيحية للنوع والنظر في الأمثلة النموذجية التي تم تنفيذها عن طريق typing
التعليقات التوضيحية.
أدوات الشرح
يتم دعم التعليقات التوضيحية للكتابة بواسطة العديد من معرفات Python IDE التي تبرز رمزًا غير صحيح أو توفر تلميحات أثناء الكتابة.
على سبيل المثال ، هذا ما يبدو عليه في Pycharm:
خطأ تسليط الضوء

نصائح:

تتم معالجة التعليقات التوضيحية أيضًا بواسطة linters وحدة التحكم.
هنا هو إخراج pylint:
$ pylint example.py ************* Module example example.py:7:6: E1101: Instance of 'int' has no 'startswith' member (no-member)
ولكن بالنسبة لنفس الملف الذي وجده mypy:
$ mypy example.py example.py:7: error: "int" has no attribute "startswith" example.py:10: error: Unsupported operand types for // ("str" and "int")
قد يختلف سلوك مختلف المحللين. على سبيل المثال ، يعالج mypy و pycharm تغيير نوع المتغير بشكل مختلف. كذلك في الأمثلة ، سأركز على إخراج mypy.
في بعض الأمثلة ، قد تعمل التعليمات البرمجية دون استثناء عند بدء التشغيل ، ولكنها قد تحتوي على أخطاء منطقية بسبب استخدام متغيرات من النوع الخطأ. وفي بعض الأمثلة ، قد لا يتم تنفيذه.
الأساسيات
على عكس الإصدارات القديمة من بيثون ، لا تتم كتابة التعليقات التوضيحية في التعليقات أو التوثيق ، ولكن مباشرة في الكود. من ناحية ، ينهار هذا التوافق التنازلي ، من ناحية أخرى ، فهذا يعني بوضوح أنه جزء من الكود ويمكن معالجته وفقًا لذلك
في أبسط الحالات ، يحتوي التعليق التوضيحي على النوع المتوقع مباشرةً. سيتم مناقشة الحالات الأكثر تعقيدًا أدناه. إذا تم تحديد الفئة الأساسية كتعليق توضيحي ، يكون تمرير مثيلات من نسلها كقيم مقبولاً. ومع ذلك ، يمكنك فقط استخدام تلك الميزات التي يتم تنفيذها في الفئة الأساسية.
تتم كتابة التعليقات التوضيحية للمتغيرات بعد النقطتين بعد المعرف. بعد ذلك ، يمكن تهيئة القيمة. على سبيل المثال
price: int = 5 title: str
يتم شرح معلمات الوظيفة بنفس طريقة المتغيرات ، وتتم الإشارة إلى قيمة الإرجاع بعد السهم ->
وقبل النقطتين الأخيرتين. على سبيل المثال
def indent_right(s: str, width: int) -> str: return " " * (max(0, width - len(s))) + s
بالنسبة لحقول الفصل ، يجب تحديد التعليقات التوضيحية بشكل صريح عند تحديد فئة. ومع ذلك ، يمكن للمحللين إخراجها تلقائيًا وفقًا للطريقة __init__
، ولكن في هذه الحالة لن يكونوا متاحين في وقت التشغيل. اقرأ المزيد حول العمل مع التعليقات التوضيحية في وقت التشغيل في الجزء الثاني من المقالة
class Book: title: str author: str def __init__(self, title: str, author: str) -> None: self.title = title self.author = author b: Book = Book(title='Fahrenheit 451', author='Bradbury')
بالمناسبة ، عند استخدام dataclass ، يجب تحديد أنواع الحقول في الفصل. المزيد عن dataclass
المدمج في أنواع
على الرغم من أنه يمكنك استخدام أنواع قياسية كتعليقات توضيحية ، يتم إخفاء الكثير من الأشياء المفيدة في وحدة typing
.
اختياري
إذا قمت بتحديد المتغير بنوع int
وحاولت تعيينه بلا ، فسيحدث خطأ:
Incompatible types in assignment (expression has type "None", variable has type "int")
في مثل هذه الحالات ، يتم توفير تعليق توضيحي Optional
بنوع معين في وحدة الكتابة. يرجى ملاحظة أن نوع المتغير الاختياري مبين بين قوسين معقوفين.
from typing import Optional amount: int amount = None
أي
في بعض الأحيان لا تريد الحد من الأنواع المحتملة للمتغير. على سبيل المثال ، إذا لم تكن مهمة حقًا ، أو إذا كنت تخطط للقيام بأنواع مختلفة بنفسك. في هذه الحالة ، يمكنك استخدام Any
تعليق توضيحي. لن أقسم Mypy على الكود التالي:
unknown_item: Any = 1 print(unknown_item) print(unknown_item.startswith("hello")) print(unknown_item // 0)
قد يطرح السؤال ، لماذا لا تستخدم object
؟ ومع ذلك ، في هذه الحالة ، يُفترض أنه على الرغم من أنه يمكن نقل أي كائن ، إلا أنه لا يمكن الوصول إليه إلا كمثيل object
.
unknown_object: object print(unknown_object) print(unknown_object.startswith("hello"))
الاتحاد
بالنسبة للحالات التي يكون فيها من الضروري السماح باستخدام ليس فقط أي أنواع ، ولكن أيضًا بعض الأنواع ، يمكنك استخدام typing.Union
الشرح.
def hundreds(x: Union[int, float]) -> int: return (int(x) // 100) % 10 hundreds(100.0) hundreds(100) hundreds("100")
بالمناسبة ، فإن التعليق التوضيحي Optional[T]
مكافئ Union[T, None]
، على الرغم من أن هذا الإدخال غير مستحسن.
مجموعات
تدعم آلية كتابة التعليقات التوضيحية الآلية العامة ( Generics ، المزيد في الجزء الثاني من المقالة) ، والتي تسمح بتحديد أنواع العناصر المخزنة فيها للحاويات.
قوائم
للإشارة إلى أن المتغير يحتوي على قائمة ، يمكنك استخدام نوع القائمة كتعليق توضيحي. ومع ذلك ، إذا كنت تريد تحديد العناصر التي تحتويها القائمة ، فلن يكون هذا التعليق التوضيحي مناسبًا بعد الآن. هناك typing.List
. قائمة لهذا الغرض. على غرار الطريقة التي حددنا بها نوع متغير اختياري ، فإننا نحدد نوع عناصر القائمة بين قوسين معقوفين.
titles: List[str] = ["hello", "world"] titles.append(100500)
من المفترض أن تحتوي القائمة على عدد غير محدد من العناصر من نفس النوع. ولكن لا توجد قيود على التعليق التوضيحي لأحد العناصر: يمكنك استخدام Any
، Optional
، List
وغيرها. إذا لم يتم تحديد نوع العنصر ، فمن المفترض أن يكون Any
.
بالإضافة إلى القائمة ، توجد تعليقات توضيحية مماثلة للمجموعات: typing.Set
و typing.FrozenSet
.
Tuples
Tuples ، على عكس القوائم ، وغالبا ما تستخدم لعناصر غير متجانسة. يشبه بناء الجملة اختلافًا واحدًا: تشير الأقواس المربعة إلى نوع كل عنصر من عناصر المجموعة بشكل فردي.
إذا كنت تخطط لاستخدام مجموعة شبيهة بالقائمة: قم بتخزين عدد غير معروف من العناصر من نفس النوع ، يمكنك استخدام علامة القطع ( ...
).
Tuple
Annotation بدون تحديد أنواع العناصر التي تعمل بشكل مشابه لـ Tuple[Any, ...]
price_container: Tuple[int] = (1,) price_container = ("hello")
قواميس
typing.Dict
، typing.Dict
استخدام typing.Dict
. يتم تعليق نوع المفتاح ونوع القيمة بشكل منفصل:
book_authors: Dict[str, str] = {"Fahrenheit 451": "Bradbury"} book_authors["1984"] = 0
وبالمثل تستخدم typing.DefaultDict
و typing.OrderedDict
نتيجة وظيفة
يمكنك استخدام أي تعليق توضيحي للإشارة إلى نوع نتيجة الوظيفة. ولكن هناك بعض الحالات الخاصة.
إذا لم تُرجع الدالة شيئًا (على سبيل المثال ، مثل print
) ، فستكون النتيجة دائمًا بلا للتعليق ، نستخدم أيضًا بلا.
ستكون الخيارات الصالحة لإنهاء مثل هذه الوظيفة هي: إرجاع صراحة بلا ، العودة دون تحديد قيمة ، وإنهاء دون استدعاء return
.
def nothing(a: int) -> None: if a == 1: return elif a == 2: return None elif a == 3: return ""
إذا لم تُرجع الدالة أبدًا (على سبيل المثال ، مثل sys.exit
) ، فيجب عليك استخدام التعليق التوضيحي NoReturn
:
def forever() -> NoReturn: while True: pass
إذا كانت دالة منشئ ، أي أن جسمها يحتوي على yield
، يمكنك استخدام Iterable[T]
أو Generator[YT, ST, RT]
للوظيفة التي تم إرجاعها:
def generate_two() -> Iterable[int]: yield 1 yield "2"
بدلا من الاستنتاج
بالنسبة إلى العديد من المواقف ، توجد أنواع مناسبة في وحدة الكتابة ، لكنني لن أفكر في كل شيء ، لأن السلوك مشابه لتلك التي تم النظر فيها.
على سبيل المثال ، هناك Iterator
كإصدار عام لـ collections.abc.Iterator
، typing.SupportsInt
للإشارة إلى أن الكائن يدعم الأسلوب __int__
، أو __int__
للوظائف والكائنات التي تدعم الأسلوب __call__
يحدد المعيار أيضًا تنسيق التعليقات التوضيحية في شكل تعليقات وملفات كعب الروتين التي تحتوي على معلومات فقط للمحللات الثابتة.
في المقالة التالية ، أود أن أتطرق إلى آلية عمل الأدوية الجنيسة ومعالجة التعليقات التوضيحية في وقت التشغيل.