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
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"))
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")
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)
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")
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
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 ""
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"
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.