Sintaks anotasi tipe Python berubah lebih cepat daripada bagian bahasa lainnya: apa yang ditulis dengan typing.Union[int, str] lima tahun lalu kini cukup ditulis int | str, dan Python 3.12 menambahkan statement type serta generic class tanpa TypeVar. Materi dasar union dan Optional telah dibahas pada Chapter Union dan Optional Types. Chapter ini menyusuri evolusinya secara utuh, menguji bagian yang bekerja pada runtime, dan merangkum praktik terbaik anotasi modern hari ini.
Evolusi Sintaks Union #
Dulu, union wajib diimpor dari module typing dan dibangun dengan Union serta Optional. Sejak Python 3.10 (PEP 604), operator | pada tipe menjadi bagian bahasa, dan hasilnya bukan sekadar gula sintaks — objek union hasil int | float benar-benar bisa dipakai pada runtime:
from typing import Optional, Union
# gaya lama (sebelum Python 3.10)
def cari_user_lama(user_id: Union[int, str]) -> Optional[str]:
return f"user-{user_id}"
# gaya modern (Python 3.10+)
def cari_user(user_id: int | str) -> str | None:
return f"user-{user_id}"
print(cari_user_lama(42))
print(cari_user("budi"))
# union bekerja pada runtime, bukan sekadar anotasi
nilai = 3.14
print(isinstance(nilai, int | float))
print(isinstance(nilai, int | str))
# union adalah objek sungguhan yang bisa disimpan dalam variabel
Angka = int | float
print(Angka)
print(isinstance(nilai, Angka))Output yang Dihasilkan:
user-42
user-budi
True
False
int | float
TrueMari kita bedah bagian-bagian pentingnya:
- Baris 4 dan 9 (
Union[int, str]vsint | str): kedua bentuk menghasilkan anotasi yang setara; interpreter Python mengevaluasiint | strmenjadi objektypes.UnionTypesaatdefdieksekusi, tanpa perlu impor apa pun. - Baris 18-19 (
isinstance(nilai, int | float)): karena union adalah objek runtime, fungsiisinstance()menerimaint | floatsebagai argumen kedua — satu panggilan menggantikan tuple(int, float). Nilai3.14lolos uji pertama dan gagal pada uji kedua. - Baris 22 (
Angka = int | float): union yang disimpan dalam variabel tetap berfungsi sebagai alat uji; barisprint(Angka)mencetak representasinya,int | float.
Konsekuensi penting: pada kode berjalan di Python 3.10+, isinstance(x, int | None) sah. Sebaliknya, klausa except tetap hanya menerima tuple atau kelas exception tunggal — objek union tidak bisa dipakai di sana. Pada Python 3.8 dan 3.9, bentuk | pada anotasi hanya aman bila evaluasinya ditunda — tekniknya dibahas pada Chapter Menjaga Kompatibilitas Versi.
Statement type dan Generic Syntax PEP 695 #
Python 3.12 (PEP 695) menambahkan dua sintaks native: statement type untuk alias tipe dan parameter generic langsung pada class/def — keduanya bersih tanpa impor TypeVar:
# Python 3.12 (PEP 695): statement type dan generic syntax baru
type ID = int | str
type Item[T] = tuple[T, str]
def pertama[U](items: list[U]) -> U:
return items[0]
class Kotak[T]:
def __init__(self, isi: T) -> None:
self.isi = isi
def ambil(self) -> T:
return self.isi
print(ID)
print(Item[int])
print(pertama(["a", "b", "c"]))
print(Kotak(99).ambil())
print(Kotak.__type_params__)Output yang Dihasilkan:
ID
Item[int]
a
99
(T,)Perhatikan detailnya:
- Baris 2 (
type ID = int | str): statementtypemembuat objekTypeAliasTypebernamaIDyang lazily dievaluasi — isiint | strtidak dievaluasi sampai benar-benar dibutuhkan, sehingga alias dapat merujuk nama yang didefinisikan lebih belakangan. - Baris 3 (
type Item[T] = tuple[T, str]): parameterTdideklarasikan langsung dalam kurung siku, menggantikanTypeVar("T")dari gaya lama;Item[int]menghasilkan alias generik yang terisi. - Baris 6 (
def pertama[U](...)): function generik kini mendeklarasikan parameternya di depan tanda kurung parameter, danlist[U]pada anotasi menandai fungsi mengembalikan elemen bertipe sama dengan isinya. - Baris 10 (
class Kotak[T]:): class generik memakai sintaks yang sama; atribut__type_params__mengekspos parametrnya sebagai tuple(T,)untuk inspeksi runtime.
Implicit Optional Telah Pensiun dan typing.List Mulai Ditinggalkan #
Dua kebiasaan lama kini resmi tidak dianjurkan. Pertama, PEP 484 semula mengizinkan def set_diskon(persen: int = None) ditafsirkan sebagai int | None; kebijakan itu telah dicabut dan static type checker kini menolaknya. Simpan sebagai diskon_salah.py lalu jalankan python3 -m mypy diskon_salah.py:
def set_diskon(persen: int = None):
return persenOutput yang Dihasilkan:
diskon_salah.py:1: error: Incompatible default for parameter "persen" (default has type "None", parameter has type "int") [assignment]
diskon_salah.py:1: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
diskon_salah.py:1: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
Found 1 error in 1 file (checked 1 source file)Bentuk yang benar menuliskan None secara eksplisit:
def set_diskon(persen: int | None = None):
return persenOutput yang Dihasilkan (python3 -m mypy diskon_benar.py):
Success: no issues found in 1 source fileKedua, alias generik pada typing seperti typing.List[int] dan typing.Dict[str, int] adalah jembatan masa lalu untuk Python 3.8; sejak Python 3.9 (PEP 585), tipe bawaan list[int] dan dict[str, int] mendukung subscript langsung. Tabel berikut merangkum posisi tiap kebiasaan hari ini:
| Anotasi | Status | Gunakan bila |
|---|---|---|
int | str, str | None | Standar saat ini | Python 3.10+ (atau anotasi ditunda) |
list[int], dict[str, int] | Standar saat ini | Python 3.9+ |
type Alias = ..., class Kotak[T] | Standar modern | Python 3.12+ |
typing.Union, typing.Optional | Masih sah, gaya lama | Mendukung Python < 3.10 |
typing.List, typing.Dict | Usang, disarankan bermigrasi | Hanya untuk Python 3.8 |
def f(x: int = None) | Ditolak type checker | Jangan — tulis int | None = None |