مقدمة لشرح بيثون. استمرار


رسم توضيحي لـ Magdalena Tomczyk


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


إعلان أولي


عادة لا يمكنك استخدام نوع قبل إنشائه. على سبيل المثال ، لن تبدأ التعليمة البرمجية التالية:


class LinkedList: data: Any next: LinkedList # NameError: name 'LinkedList' is not defined 

لإصلاح ذلك ، يجوز استخدام سلسلة حرفية. في هذه الحالة ، سيتم حساب التعليقات التوضيحية المؤجلة.


 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()' # error: invalid type comment or annotation 

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) # error: Argument 2 to "app" has incompatible type "Callable[[], None]"; expected "Callable[[int], str]" 

يجوز تحديد نوع الإرجاع للدالة فقط دون تحديد معلماتها. في هذه الحالة ، يتم استخدام علامة القطع: 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 # error: Incompatible types in assignment (expression has type "int", variable has type "LinkedList[int]") head_int.data += 1 head_int.data.replace("0", "1") # error: "int" has no attribute "replace" head_str: LinkedList[str] = LinkedList("1") head_str.data.replace("0", "1") head_str = LinkedList[str](1) # error: Argument 1 to "LinkedList" has incompatible type "int"; expected "str" 

كما ترون ، بالنسبة للأنواع العامة ، يعمل الاستدلال التلقائي لنوع المعلمة.


إذا لزم الأمر ، يمكن أن يحتوي النوع العام على أي عدد من المعلمات: عام Generic[T1, T2, T3] .


أيضًا ، عند تعريف TypeVar ، يمكنك تقييد أنواع صالحة:


 T2 = TypeVar("T2", int, float) class SomethingNumeric(Generic[T2]): pass x = SomethingNumeric[str]() # error: Value of type variable "T2" of "SomethingNumeric" cannot be "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) # error: Missing positional argument "b" in call to "mysum" 

العمل مع الشروح وقت التشغيل


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


 def render_int(num: int) -> str: return str(num) print(render_int.annotations) # {'num': <class 'int'>, 'return': <class 'str'>} 

get_type_hints متاح أيضًا - تقوم بإرجاع التعليقات التوضيحية للكائن الذي تم تمريره إليها ، وفي كثير من الحالات تتطابق مع محتويات __annotations__ ، لكن هناك اختلافات: كما تضيف التعليقات التوضيحية للكائنات __mro__ (بترتيب عكسي لـ __mro__ ) ، كما تتيح الإعلانات الأولية للأنواع المحددة __mro__ .


 T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" print(LinkedList.__annotations__) # {'data': ~T, 'next': 'LinkedList[T]'} print(get_type_hints(LinkedList)) # {'data': ~T, 'next': __main__.LinkedList[~T]} 

بالنسبة للأنواع العامة ، تتوفر معلومات حول النوع نفسه ومعلماته من خلال __origin__ و __args__ ، لكن هذا ليس جزءًا من المعيار وقد تغير السلوك بالفعل بين الإصدارين 3.6 و 3.7

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


All Articles