Pengantar Anotasi Jenis Python

Pendahuluan



Ilustrasi oleh Magdalena Tomczyk


Bagian kedua


Python adalah bahasa dengan pengetikan dinamis dan memungkinkan kita untuk secara bebas memanipulasi variabel dari tipe yang berbeda. Namun, ketika menulis kode, dengan satu atau lain cara kita mengasumsikan jenis variabel apa yang akan digunakan (ini mungkin disebabkan oleh pembatasan algoritma atau logika bisnis). Dan untuk pengoperasian program yang benar, penting bagi kami untuk menemukan kesalahan sedini mungkin terkait dengan transfer data dari jenis yang salah.


Menjaga gagasan bebek dinamis mengetik dalam versi modern Python (3.6+), mendukung anotasi tipe variabel, bidang kelas, argumen, dan mengembalikan nilai fungsi:



Jenis anotasi hanya dibaca oleh juru bahasa Python dan tidak lagi diproses, tetapi tersedia untuk digunakan dari kode pihak ketiga dan terutama dirancang untuk digunakan oleh analisis statis.


Nama saya Andrey Tikhonov dan saya terlibat dalam pengembangan backend di Lamoda.


Pada artikel ini, saya ingin menjelaskan dasar-dasar penggunaan anotasi jenis dan mempertimbangkan contoh khas yang diterapkan dengan typing anotasi.


Alat Anotasi


Jenis anotasi didukung oleh banyak IDE Python yang menyoroti kode yang salah atau memberikan petunjuk saat Anda mengetik.


Sebagai contoh, ini seperti apa di Pycharm:


Menyoroti kesalahan



Kiat:



Jenis anotasi juga diproses oleh konsol konsol.


Berikut ini adalah output dari pylint:


 $ pylint example.py ************* Module example example.py:7:6: E1101: Instance of 'int' has no 'startswith' member (no-member) 

Tetapi untuk file yang sama yang ditemukan mypy:


 $ mypy example.py example.py:7: error: "int" has no attribute "startswith" example.py:10: error: Unsupported operand types for // ("str" and "int") 

Perilaku analis yang berbeda dapat bervariasi. Sebagai contoh, mypy dan pycharm menangani perubahan tipe variabel secara berbeda. Lebih jauh dalam contoh, saya akan fokus pada output mypy.


Dalam beberapa contoh, kode dapat berjalan tanpa kecuali saat startup, tetapi mungkin mengandung kesalahan logis karena penggunaan variabel dari tipe yang salah. Dan dalam beberapa contoh, itu mungkin bahkan tidak dieksekusi.


Dasar-dasarnya


Tidak seperti versi Python yang lebih lama, anotasi jenis tidak ditulis dalam komentar atau docstring, tetapi langsung dalam kode. Di satu sisi, ini memecah kompatibilitas ke bawah, di sisi lain, itu jelas berarti bahwa itu adalah bagian dari kode dan dapat diproses sesuai


Dalam kasus paling sederhana, anotasi berisi tipe yang diharapkan secara langsung. Kasus-kasus yang lebih kompleks akan dibahas di bawah ini. Jika kelas dasar ditetapkan sebagai anotasi, melewati turunan turunannya sebagai nilai dapat diterima. Namun, Anda hanya dapat menggunakan fitur-fitur yang diterapkan di kelas dasar.


Anotasi untuk variabel ditulis setelah titik dua setelah pengidentifikasi. Setelah ini, nilainya dapat diinisialisasi. Sebagai contoh


 price: int = 5 title: str 

Parameter fungsi dianotasi dengan cara yang sama seperti variabel, dan nilai kembali ditunjukkan setelah panah -> dan sebelum titik dua akhir. Sebagai contoh


 def indent_right(s: str, width: int) -> str: return " " * (max(0, width - len(s))) + s 

Untuk bidang kelas, anotasi harus ditentukan secara eksplisit saat mendefinisikan kelas. Namun, penganalisis dapat mengeluarkannya secara otomatis berdasarkan metode __init__ , tetapi dalam kasus ini mereka tidak akan tersedia saat runtime. Baca selengkapnya tentang bekerja dengan anotasi dalam runtime di bagian kedua artikel


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

Omong-omong, saat menggunakan dataclass, tipe bidang harus ditentukan di kelas. Lebih lanjut tentang dataclass


Jenis bawaan


Meskipun Anda dapat menggunakan tipe standar sebagai anotasi, banyak hal berguna yang tersembunyi di modul typing .


Opsional


Jika Anda menandai variabel dengan tipe int dan mencoba menetapkannya None , akan ada kesalahan:


Incompatible types in assignment (expression has type "None", variable has type "int")


Untuk kasus seperti itu, anotasi Optional dengan jenis tertentu disediakan dalam modul pengetikan. Harap dicatat bahwa jenis variabel opsional ditunjukkan dalam tanda kurung.


 from typing import Optional amount: int amount = None # Incompatible types in assignment (expression has type "None", variable has type "int") price: Optional[int] price = None 

Apa saja


Terkadang Anda tidak ingin membatasi jenis variabel yang mungkin. Misalnya, jika itu benar-benar tidak penting, atau jika Anda berencana untuk melakukan pemrosesan dari jenis yang berbeda sendiri. Dalam hal ini, Anda dapat menggunakan anotasi Any . Mypy tidak akan bersumpah pada kode berikut:


 unknown_item: Any = 1 print(unknown_item) print(unknown_item.startswith("hello")) print(unknown_item // 0) 

Mungkin timbul pertanyaan, mengapa tidak menggunakan object ? Namun, dalam hal ini, diasumsikan bahwa meskipun objek apa pun dapat ditransfer, ia hanya dapat diakses sebagai instance object .


 unknown_object: object print(unknown_object) print(unknown_object.startswith("hello")) # error: "object" has no attribute "startswith" print(unknown_object // 0) # error: Unsupported operand types for // ("object" and "int") 

Serikat pekerja


Untuk kasus-kasus ketika perlu untuk memungkinkan penggunaan tidak hanya jenis apa pun, tetapi hanya beberapa, Anda dapat menggunakan typing.Union Anotasi typing.Union dengan daftar jenis dalam kurung kotak.


 def hundreds(x: Union[int, float]) -> int: return (int(x) // 100) % 10 hundreds(100.0) hundreds(100) hundreds("100") # Argument 1 to "hundreds" has incompatible type "str"; expected "Union[int, float]" 

Omong-omong, anotasi Optional[T] setara dengan Union[T, None] , meskipun entri seperti itu tidak disarankan.


Koleksi


Mekanisme anotasi jenis mendukung mekanisme generik ( Generik , lebih banyak di bagian kedua artikel), yang memungkinkan menentukan jenis elemen yang disimpan di dalamnya untuk wadah.


Daftar


Untuk menunjukkan bahwa suatu variabel berisi daftar, Anda dapat menggunakan jenis daftar sebagai anotasi. Namun, jika Anda ingin menentukan elemen apa yang terdapat dalam daftar, anotasi seperti itu tidak lagi cocok. Ada typing.List . typing.List untuk ini. Mirip dengan bagaimana kami menentukan jenis variabel opsional, kami menentukan jenis item daftar dalam tanda kurung.


 titles: List[str] = ["hello", "world"] titles.append(100500) # Argument 1 to "append" of "list" has incompatible type "int"; expected "str" titles = ["hello", 1] # List item 1 has incompatible type "int"; expected "str" items: List = ["hello", 1] 

Diasumsikan bahwa daftar berisi jumlah elemen yang tidak terbatas dari jenis yang sama. Tetapi tidak ada batasan pada anotasi elemen: Anda dapat menggunakan Any , Optional , List dan lainnya. Jika jenis barang tidak ditentukan, diasumsikan sebagai Any .


Selain daftar, anotasi serupa adalah untuk set: typing.FrozenSet dan typing.FrozenSet .


Tuples


Tuples, tidak seperti daftar, sering digunakan untuk elemen heterogen. Sintaksnya sama dengan satu perbedaan: tanda kurung siku menunjukkan jenis setiap elemen tupel secara individual.


Jika Anda berencana untuk menggunakan tuple yang mirip dengan daftar: menyimpan sejumlah elemen yang tidak diketahui dari jenis yang sama, Anda dapat menggunakan ellipsis ( ... ).


Anotasi Tuple tanpa menentukan jenis elemen bekerja mirip dengan Tuple[Any, ...]


 price_container: Tuple[int] = (1,) price_container = ("hello") # Incompatible types in assignment (expression has type "str", variable has type "Tuple[int]") price_container = (1, 2) # Incompatible types in assignment (expression has type "Tuple[int, int]", variable has type "Tuple[int]") price_with_title: Tuple[int, str] = (1, "hello") prices: Tuple[int, ...] = (1, 2) prices = (1, ) prices = (1, "str") # Incompatible types in assignment (expression has type "Tuple[int, str]", variable has type "Tuple[int, ...]") something: Tuple = (1, 2, "hello") 

Kamus


Untuk kamus, typing.Dict digunakan. Jenis kunci dan tipe nilai dianotasi secara terpisah:


 book_authors: Dict[str, str] = {"Fahrenheit 451": "Bradbury"} book_authors["1984"] = 0 # Incompatible types in assignment (expression has type "int", target has type "str") book_authors[1984] = "Orwell" # Invalid index type "int" for "Dict[str, str]"; expected type "str" 

Demikian pula yang digunakan typing.DefaultDict dan typing.OrderedDict


Hasil fungsi


Anda dapat menggunakan anotasi apa pun untuk menunjukkan jenis hasil fungsi. Tetapi ada beberapa kasus khusus.


Jika suatu fungsi tidak mengembalikan apa pun (misalnya, seperti print ), hasilnya selalu None . Untuk anotasi, kami juga menggunakan None .


Opsi yang valid untuk mengakhiri fungsi seperti itu adalah: secara eksplisit mengembalikan None , kembali tanpa menentukan nilai, dan mengakhiri tanpa memanggil return .


 def nothing(a: int) -> None: if a == 1: return elif a == 2: return None elif a == 3: return "" # No return value expected else: pass 

Jika fungsi tidak pernah kembali (misalnya, seperti sys.exit ), Anda harus menggunakan anotasi NoReturn :


 def forever() -> NoReturn: while True: pass 

Jika itu adalah fungsi generator, yaitu, tubuhnya berisi yield , Anda dapat menggunakan anotasi Iterable[T] atau Generator[YT, ST, RT] untuk fungsi yang dikembalikan:


 def generate_two() -> Iterable[int]: yield 1 yield "2" # Incompatible types in "yield" (actual type "str", expected type "int") 

Alih-alih sebuah kesimpulan


Untuk banyak situasi, ada jenis yang sesuai dalam modul pengetikan, namun saya tidak akan mempertimbangkan semuanya, karena perilakunya mirip dengan yang dipertimbangkan.
Misalnya, ada Iterator sebagai versi generik untuk collections.abc.Iterator , typing.SupportsInt MendukungInt untuk menunjukkan bahwa objek mendukung metode __int__ , atau Callable untuk fungsi dan objek yang mendukung metode __call__


Standar ini juga mendefinisikan format anotasi dalam bentuk komentar dan file rintisan yang berisi informasi hanya untuk analisis statis.


Pada artikel selanjutnya saya ingin membahas mekanisme kerja generik dan pemrosesan anotasi dalam runtime.

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


All Articles