
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
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'
KomentarSecara 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()'
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)
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
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]()
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)
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)
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__)
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