
رسم توضيحي لـ Magdalena Tomczyk
في الجزء الأول من المقالة ، وصفت أساسيات استخدام التعليقات التوضيحية للنوع. ومع ذلك ، لم يتم النظر في عدة نقاط مهمة. أولاً ، الأدوية الجنيسة هي آلية مهمة ، وثانيًا ، قد يكون من المفيد في بعض الأحيان العثور على معلومات حول الأنواع المتوقعة في وقت التشغيل. لكنني أردت أن أبدأ بأشياء أبسط
إعلان أولي
عادة لا يمكنك استخدام نوع قبل إنشائه. على سبيل المثال ، لن تبدأ التعليمة البرمجية التالية:
class LinkedList: data: Any next: LinkedList
لإصلاح ذلك ، يجوز استخدام سلسلة حرفية. في هذه الحالة ، سيتم حساب التعليقات التوضيحية المؤجلة.
class LinkedList: data: Any next: 'LinkedList'
يمكنك أيضًا الوصول إلى الفئات من الوحدات النمطية الأخرى (بالطبع ، إذا تم استيراد الوحدة النمطية): some_variable: 'somemodule.SomeClass'
ملاحظةوبصفة عامة ، يمكن استخدام أي تعبير محسوب كتعليق توضيحي. ومع ذلك ، يوصى بإبقائها بسيطة قدر الإمكان حتى تتمكن أدوات التحليل الثابتة من استخدامها. على وجه الخصوص ، فهم لن يفهموا على الأرجح الأنواع المحسوبة ديناميكيًا. اقرأ المزيد حول القيود هنا: PEP 484 - اكتب تلميحات # تلميحات النوع المقبول
على سبيل المثال ، ستعمل التعليمة البرمجية التالية وحتى التعليقات التوضيحية ستكون متاحة في وقت التشغيل ، ولكن mypy سوف يلقي عليها خطأ
def get_next_type(arg=None): if arg: return LinkedList else: return Any class LinkedList: data: Any next: 'get_next_type()'
UPD : في Python 4.0 ، تم التخطيط لتمكين حساب التعليقات التوضيحية المتأخرة ( PEP 563 ) ، مما سيزيل هذه التقنية بحرف السلسلة. باستخدام Python 3.7 ، يمكنك تمكين سلوك جديد باستخدام إنشاء from __future__ import annotations
وظائف والكائنات دعا
في الحالات التي تحتاج فيها إلى نقل دالة أو كائن آخر (على سبيل المثال ، كرد اتصال) ، تحتاج إلى استخدام التعليق التوضيحي Callable [[ArgType1 ، ArgType2 ، ...] ، ReturnType]
على سبيل المثال
def help() -> None: print("This is help string") def render_hundreds(num: int) -> str: return str(num // 100) def app(helper: Callable[[], None], renderer: Callable[[int], str]): helper() num = 12345 print(renderer(num)) app(help, render_hundreds) app(help, help)
يجوز تحديد نوع الإرجاع للدالة فقط دون تحديد معلماتها. في هذه الحالة ، يتم استخدام علامة القطع: Callable[..., ReturnType]
. لاحظ أنه لا توجد أقواس مربعة حول علامة القطع.
في الوقت الحالي ، من المستحيل وصف توقيع دالة مع عدد متغير من المعلمات من نوع معين أو تحديد الوسائط المسماة.
أنواع عامة
في بعض الأحيان يكون من الضروري حفظ معلومات حول نوع ما ، دون إصلاحه بشكل صارم. على سبيل المثال ، إذا كتبت حاوية تخزن نفس البيانات. أو دالة تقوم بإرجاع بيانات من نفس النوع كأحد الوسائط.
هناك أنواع مثل List أو Callable ، والتي رأيناها سابقًا فقط استخدم الآلية العامة. ولكن إلى جانب الأنواع القياسية ، يمكنك إنشاء أنواع عامة خاصة بك. للقيام بذلك ، أولاً ، احصل على متغير TypeVar ، والذي سيكون سمة من سمات النوع العام ، وثانياً ، أعلن بشكل مباشر نوعًا عامًا:
T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" def __init__(self, data: T): self.data = data head_int: LinkedList[int] = LinkedList(1) head_int.next = LinkedList(2) head_int.next = 2
كما ترون ، بالنسبة للأنواع العامة ، يعمل الاستدلال التلقائي لنوع المعلمة.
إذا لزم الأمر ، يمكن أن يحتوي النوع العام على أي عدد من المعلمات: عام Generic[T1, T2, T3]
.
أيضًا ، عند تعريف TypeVar ، يمكنك تقييد أنواع صالحة:
T2 = TypeVar("T2", int, float) class SomethingNumeric(Generic[T2]): pass x = SomethingNumeric[str]()
يلقي
في بعض الأحيان لا يمكن للمحلل الثابت للمحلل تحديد نوع المتغير بشكل صحيح ، في هذه الحالة ، يمكنك استخدام وظيفة cast. وتتمثل مهمتها الوحيدة في إظهار المحلل أن التعبير من نوع معين. على سبيل المثال:
from typing import List, cast def find_first_str(a: List[object]) -> str: index = next(i for i, x in enumerate(a) if isinstance(x, str)) return cast(str, a[index])
يمكن أن يكون مفيدًا أيضًا للديكور:
MyCallable = TypeVar("MyCallable", bound=Callable) def logged(func: MyCallable) -> MyCallable: @wraps(func) def wrapper(*args, **kwargs): print(func.__name__, args, kwargs) return func(*args, **kwargs) return cast(MyCallable, wrapper) @logged def mysum(a: int, b: int) -> int: return a + b mysum(a=1)
العمل مع الشروح وقت التشغيل
على الرغم من أن المترجم الفوري لا يستخدم التعليقات التوضيحية من تلقاء نفسه ، إلا أنها متاحة لرمزك أثناء تشغيل البرنامج. لهذا الغرض ، يتم __annotations__
سمة كائن __annotations__
تحتوي على قاموس مع التعليقات التوضيحية المشار إليها. بالنسبة للوظائف ، هذه هي تعليقات توضيحية للمعلمات ونوع الإرجاع وكائن وتعليقات توضيحية على المجال للنطاق العام والمتغيرات وتعليقاتها التوضيحية.
def render_int(num: int) -> str: return str(num) print(render_int.annotations)
get_type_hints
متاح أيضًا - تقوم بإرجاع التعليقات التوضيحية للكائن الذي تم تمريره إليها ، وفي كثير من الحالات تتطابق مع محتويات __annotations__
، لكن هناك اختلافات: كما تضيف التعليقات التوضيحية للكائنات __mro__
(بترتيب عكسي لـ __mro__
) ، كما تتيح الإعلانات الأولية للأنواع المحددة __mro__
.
T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" print(LinkedList.__annotations__)
بالنسبة للأنواع العامة ، تتوفر معلومات حول النوع نفسه ومعلماته من خلال __origin__
و __args__
، لكن هذا ليس جزءًا من المعيار وقد تغير السلوك بالفعل بين الإصدارين 3.6 و 3.7