Anotasi Jenis Baru di Python 3.8 (Protokol, Final, TypedDict, Literal)

Python 3.8 keluar malam ini dan mengetik anotasi mendapat fitur baru:


  • Protokol
  • Kamus yang Diketik
  • Specifier akhir
  • Pencocokan nilai tetap

Jika Anda belum terbiasa dengan anotasi jenis, saya sarankan memperhatikan artikel saya sebelumnya ( awal , lanjutan )
Dan sementara semua orang khawatir tentang walrus, saya ingin berbicara singkat tentang yang terbaru dalam modul pengetikan


Protokol


Python menggunakan pengetikan bebek dan kelas tidak diwajibkan untuk mewarisi dari antarmuka tertentu, seperti dalam beberapa bahasa lainnya.
Sayangnya, sebelum versi 3.8, kami tidak dapat mengungkapkan persyaratan yang diperlukan untuk objek menggunakan anotasi jenis.
PEP 544 dirancang untuk mengatasi masalah ini.


Istilah seperti "protokol iterator" atau "protokol deskriptor" sudah akrab dan telah digunakan untuk waktu yang lama.
Sekarang Anda dapat menggambarkan protokol dalam bentuk kode dan memeriksa kepatuhannya pada tahap analisis statis.


Perlu dicatat bahwa, dimulai dengan Python 3.6, modul pengetikan sudah mencakup beberapa protokol standar.
Misalnya, SupportsInt (yang membutuhkan metode __int__ ), SupportsBytes (membutuhkan __bytes__ ), dan beberapa lainnya.


Deskripsi Protokol


Protokol dijelaskan sebagai kelas reguler yang diturunkan dari Protokol. Itu dapat memiliki metode (termasuk yang dengan implementasi) dan bidang.
Kelas nyata yang mengimplementasikan protokol dapat mewarisi darinya, tetapi ini tidak perlu.


 from abc import abstractmethod from typing import Protocol, Iterable class SupportsRoar(Protocol): @abstractmethod def roar(self) -> None: raise NotImplementedError class Lion(SupportsRoar): def roar(self) -> None: print("roar") class Tiger: def roar(self) -> None: print("roar") class Cat: def meow(self) -> None: print("meow") def roar_all(bigcats: Iterable[SupportsRoar]) -> None: for t in bigcats: t.roar() roar_all([Lion(), Tiger()]) # ok roar_all([Cat()]) # error: List item 0 has incompatible type "Cat"; expected "SupportsRoar" 

Kita bisa menggabungkan protokol menggunakan warisan, membuat yang baru.
Namun, dalam hal ini, Anda juga harus secara eksplisit menentukan Protokol sebagai kelas induk.


 class BigCatProtocol(SupportsRoar, Protocol): def purr(self) -> None: print("purr") 

Generik, diketik sendiri, bisa dipanggil


Protokol, seperti kelas reguler, bisa menjadi generik. Alih-alih menetapkan Protocol dan Generic[T, S,...] sebagai orang tua Generic[T, S,...] Anda cukup menentukan Protocol[T, S,...]


Jenis protokol penting lainnya adalah mengetik sendiri (lihat PEP 484 ). Sebagai contoh


 C = TypeVar('C', bound='Copyable') class Copyable(Protocol): def copy(self: C) -> C: class One: def copy(self) -> 'One': ... 

Selain itu, protokol dapat digunakan ketika sintaks anotasi Callable tidak cukup.
Cukup jelaskan protokol dengan metode __call__ tanda tangan yang diinginkan


Pemeriksaan runtime


Meskipun protokol terutama dirancang untuk digunakan oleh analisis statis, kadang-kadang perlu untuk memeriksa apakah kelas milik protokol yang diinginkan.
Untuk memungkinkan ini, terapkan dekorator @runtime_checkable ke protokol dan pemeriksaan isinstance / issubclass akan mulai memeriksa kepatuhan terhadap protokol


Namun, fitur ini memiliki sejumlah batasan penggunaan. Secara khusus, obat generik tidak didukung


Kamus yang Diketik


Kelas (khususnya, kelas data ) atau tupel bernama biasanya digunakan untuk mewakili data terstruktur.
tetapi kadang-kadang, misalnya, dalam kasus deskripsi struktur json, dapat bermanfaat untuk memiliki kamus dengan kunci tertentu.
PEP 589 memperkenalkan konsep TypedDict , yang sebelumnya tersedia dalam ekstensi dari mypy


Seperti dataclasses atau tuple yang diketik, ada dua cara untuk mendeklarasikan kamus yang diketik. Dengan warisan atau menggunakan pabrik:


 class Book(TypedDict): title: str author: str AlsoBook = TypedDict("AlsoBook", {"title": str, "author": str}) # same as Book book: Book = {"title": "Fareneheit 481", "author": "Bradbury"} # ok other_book: Book = {"title": "Highway to Hell", "artist": "AC/DC"} # error: Extra key 'artist' for TypedDict "Book" another_book: Book = {"title": "Fareneheit 481"} # error: Key 'author' missing for TypedDict "Book" 

Kamus yang diketik mendukung warisan:


 class BookWithDesc(Book): desc: str 

Secara default, semua kunci kamus diperlukan, tetapi Anda dapat menonaktifkan ini dengan melewatkan total=False saat membuat kelas.
Ini hanya berlaku untuk kunci yang dijelaskan dalam box office saat ini dan tidak mempengaruhi warisan


 class SimpleBook(TypedDict, total=False): title: str author: str simple_book: SimpleBook = {"title": "Fareneheit 481"} # ok 

Menggunakan TypedDict memiliki sejumlah keterbatasan. Khususnya:


  • pemeriksaan runtime melalui isinstance tidak didukung
  • kunci harus berupa nilai literal atau final

Selain itu, operasi yang tidak aman seperti .clear atau del dilarang dengan kamus semacam itu.
Bekerja pada kunci yang bukan literal juga dapat dilarang, karena dalam hal ini tidak mungkin untuk menentukan jenis nilai yang diharapkan


Pengubah akhir


PEP 591 memperkenalkan pengubah akhir (sebagai dekorator dan anotasi) untuk beberapa tujuan


  • Penunjukan kelas dari mana tidak mungkin untuk diwarisi:

 from typing import final @final class Childfree: ... class Baby(Childfree): # error: Cannot inherit from final class "Childfree" ... 

  • Penunjukan metode yang dilarang menimpa:

 from typing import final class Base: @final def foo(self) -> None: ... class Derived(Base): def foo(self) -> None: # error: Cannot override final attribute "foo" (previously declared in base class "Base") ... 

  • Penunjukan variabel (parameter fungsi. Bidang kelas), yang dilarang untuk ditugaskan kembali.

 ID: Final[float] = 1 ID = 2 # error: Cannot assign to final name "ID" SOME_STR: Final = "Hello" SOME_STR = "oops" # error: Cannot assign to final name "SOME_STR" letters: Final = ['a', 'b'] letters.append('c') # ok class ImmutablePoint: x: Final[int] y: Final[int] # error: Final name must be initialized with a value def __init__(self) -> None: self.x = 1 # ok ImmutablePoint().x = 2 # error: Cannot assign to final attribute "x" 

Dalam hal ini, kode bentuk self.id: Final = 123 , tetapi hanya dalam metode __init__


Secara harfiah


Literal tipe yang didefinisikan dalam PEP 586 digunakan ketika Anda perlu memeriksa secara harfiah pada nilai-nilai spesifik


Misalnya, Literal[42] berarti bahwa hanya 42 yang diharapkan sebagai nilai.
Penting bahwa tidak hanya kesetaraan nilai diperiksa, tetapi juga jenisnya (misalnya, tidak mungkin untuk menggunakan False jika 0 diharapkan).


 def give_me_five(x: Literal[5]) -> None: pass give_me_five(5) # ok give_me_five(5.0) # error: Argument 1 to "give_me_five" has incompatible type "float"; expected "Literal[5]" give_me_five(42) # error: Argument 1 to "give_me_five" has incompatible type "Literal[42]"; expected "Literal[5]" 

Dalam hal ini, beberapa nilai dapat ditransmisikan dalam tanda kurung, yang setara dengan menggunakan Union (jenis nilai mungkin tidak bersamaan).


Ekspresi (misalnya, Literal[1+2] ) atau nilai dari tipe yang tidak bisa diubah tidak dapat digunakan sebagai nilai.


Sebagai salah satu contoh yang bermanfaat, menggunakan Literal adalah fungsi open() , yang mengharapkan nilai mode tertentu.


Ketik penanganan dalam runtime


Jika Anda ingin memproses berbagai jenis informasi (seperti saya ) saat program sedang berjalan,
fungsi get_origin dan get_args sekarang tersedia.


Jadi, untuk tipe formulir X[Y, Z,...] tipe X akan dikembalikan sebagai asal, dan (Y, Z, ...) sebagai argumen
Perlu dicatat bahwa jika X adalah alias untuk tipe bawaan atau jenis dari modul collections , maka akan diganti oleh yang asli.


 assert get_origin(Dict[str, int]) is dict assert get_args(Dict[int, str]) == (int, str) assert get_origin(Union[int, str]) is Union assert get_args(Union[int, str]) == (int, str) 

Sayangnya, fungsi untuk __parameters__ tidak


Referensi


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


All Articles