Pengantar anotasi jenis Python. Lanjutan


Ilustrasi oleh Magdalena Tomczyk


Di bagian pertama artikel, saya menjelaskan dasar-dasar penggunaan anotasi jenis. Namun, beberapa poin penting tidak dipertimbangkan. Pertama, obat generik adalah mekanisme yang penting, dan kedua, kadang-kadang berguna untuk mengetahui informasi tentang tipe yang diharapkan dalam runtime. Tetapi saya ingin memulai dengan hal-hal yang lebih sederhana


Pengumuman awal


Biasanya Anda tidak dapat menggunakan jenis sebelum dibuat. Misalnya, kode berikut bahkan tidak akan memulai:


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

Untuk memperbaikinya, diizinkan menggunakan string literal. Dalam hal ini, anotasi akan dihitung ditangguhkan.


 class LinkedList: data: Any next: 'LinkedList' 

Anda juga dapat mengakses kelas dari modul lain (tentu saja, jika modul diimpor): some_variable: 'somemodule.SomeClass'


Komentar

Secara umum, setiap ekspresi yang dapat dihitung dapat digunakan sebagai anotasi. Namun, disarankan agar disimpan sesederhana mungkin sehingga utilitas analisis statis dapat menggunakannya. Secara khusus, mereka kemungkinan besar tidak akan memahami jenis yang dapat dihitung secara dinamis. Baca lebih lanjut tentang batasan di sini: PEP 484 - Ketik Petunjuk # Petunjuk jenis yang dapat diterima


Misalnya, kode berikut ini akan berfungsi dan bahkan anotasi akan tersedia pada saat runtime, namun mypy akan melemparkan kesalahan padanya


 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 : Dalam Python 4.0, direncanakan untuk menyertakan perhitungan anotasi jenis ditangguhkan ( PEP 563 ), yang akan menyingkirkan teknik ini dengan string literal. dengan Python 3.7 Anda dapat mengaktifkan perilaku baru menggunakan konstruk from __future__ import annotations


Fungsi dan Objek yang Dipanggil


Untuk situasi ketika Anda perlu mentransfer fungsi atau objek lain (misalnya, sebagai panggilan balik), Anda perlu menggunakan anotasi Callable [[ArgType1, ArgType2, ...], ReturnType]
Sebagai contoh


 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]" 

Diijinkan untuk menentukan hanya tipe kembalinya fungsi tanpa menentukan parameternya. Dalam kasus ini, elipsis digunakan: Callable[..., ReturnType] . Perhatikan bahwa tidak ada tanda kurung siku di sekitar elipsis.


Saat ini, tidak mungkin untuk menggambarkan tanda tangan fungsi dengan sejumlah variabel parameter dari jenis tertentu atau menentukan argumen bernama.


Jenis Generik


Terkadang perlu menyimpan informasi tentang suatu jenis, tanpa memperbaikinya secara kaku. Misalnya, jika Anda menulis sebuah wadah yang menyimpan data yang sama. Atau fungsi yang mengembalikan data dengan tipe yang sama dengan salah satu argumen.


Jenis seperti Daftar atau Callable, yang kita lihat sebelumnya hanya menggunakan mekanisme generik. Tetapi selain tipe standar, Anda dapat membuat tipe generik Anda sendiri. Untuk melakukan ini, pertama, dapatkan variabel TypeVar, yang akan menjadi atribut generik, dan kedua, secara langsung mendeklarasikan tipe generik:


 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" 

Seperti yang Anda lihat, untuk tipe generik, inferensi otomatis dari tipe parameter berfungsi.


Jika diperlukan, generik dapat memiliki sejumlah parameter: Generic[T1, T2, T3] .


Juga, ketika mendefinisikan TypeVar, Anda dapat membatasi tipe yang valid:


 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


Kadang-kadang analyzer static analyzer tidak dapat menentukan jenis variabel dengan benar, dalam hal ini, Anda dapat menggunakan fungsi cor. Satu-satunya tugas adalah untuk menunjukkan kepada penganalisa bahwa ekspresi adalah tipe tertentu. Sebagai contoh:


 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]) 

Ini juga dapat bermanfaat bagi dekorator:


 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" 

Bekerja dengan anotasi runtime


Meskipun penerjemah tidak menggunakan anotasi sendiri, mereka tersedia untuk kode Anda saat program sedang berjalan. Untuk ini, __annotations__ atribut objek __annotations__ yang berisi kamus dengan anotasi yang ditunjukkan. Untuk fungsi, ini adalah anotasi parameter dan tipe pengembalian, untuk objek, anotasi bidang, untuk cakupan global, variabel, dan anotasi mereka.


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

get_type_hints juga tersedia - ia mengembalikan anotasi untuk objek yang diteruskan padanya, dalam banyak situasi ini cocok dengan konten __annotations__ , tetapi ada perbedaan: ia juga menambahkan anotasi objek induk (dalam urutan terbalik __mro__ ), dan juga memungkinkan deklarasi awal dari jenis yang ditentukan sebagai string.


 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]} 

Untuk tipe generik, informasi tentang tipe itu sendiri dan parameternya tersedia melalui __args__ dan __args__ , tetapi ini bukan bagian dari standar dan perilakunya telah berubah antara versi 3.6 dan 3.7

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


All Articles