Data nyata tidak selalu berbentuk rapi. Sebuah harga mungkin berupa angka, tetapi kolom bebasnya kadang berupa teks; hasil pencarian user mungkin ada, mungkin tidak ada. Anotasi satu tipe seperti str tidak bisa menggambarkan dua kemungkinan tersebut. Union type menjawab kebutuhan ini dengan cara yang jujur: sebutkan semua kemungkinan tipe secara eksplisit, lalu tunjukkan ke checker bagaimana setiap kemungkinan ditangani.
Menyatakan Beberapa Kemungkinan dengan Union #
Sejak Python 3.10, union ditulis dengan operator pipa | di antara tipe-tipe anggotanya:
def format_harga(harga: int | str) -> str:
if isinstance(harga, str):
return harga.upper()
return f"Rp{harga:,}".replace(",", ".")
print(format_harga("murah"))
print(format_harga(150000))Output yang Dihasilkan:
MURAH
Rp150.000Mari kita telaah baris-barisnya:
- Baris 1 (
harga: int | str): parameterhargaberarti bisa menerima integer maupun string — checker menandai keduanya sebagai kemungkinan sah. - Baris 2 (
isinstance(harga, str)): pemeriksaanisinstancemempersempit (narrowing) kemungkinan di dalam blokifmenjadi hanyastr, sehingga method.upper()di baris 3 diperbolehkan. - Baris 4 (blok
elsetersirat): setelah cabangstrditangani, checker tahu yang tersisa hanyaint, sehingga format:,untuk pemisah ribuan aman dipakai.
Kombinasi union dan isinstance adalah pola paling penting di chapter ini: deklarasikan semua kemungkinan di signature, lalu persempit di badan function sebelum memakai method milik salah satu anggota. Tanpa penyempitan, checker menolak — dan penolakan tersebut kita lihat sebentar lagi.
Untuk kode yang harus mendukung Python 3.9, tersedia bentuk lama Union[int, str] dari module typing; maknanya identik dengan int | str.
Optional: Kemungkinan Tidak Ada Nilai #
Kasus khusus union yang paling sering muncul adalah “nilai, atau kosong”. Python menyatakannya dengan None:
def cari_user(user_id: int) -> str | None:
database = {1: "Budi", 2: "Siti"}
return database.get(user_id)
print(cari_user(1))
print(cari_user(99))Output yang Dihasilkan:
Budi
NoneMethod dict.get() mengembalikan None ketika key tidak ditemukan, sehingga str | None adalah kejujuran tipe dari cari_user. Bentuk lama untuk maksud yang sama adalah Optional[str] dari module typing — sejak Python 3.10, keduanya benar-benar setara dan saling menggantikan; Optional[str] hanyalah nama lain untuk str | None, bukan “opsional boleh tidak ada parameternya”.
Idiom standar untuk parameter yang bisa dikosongkan adalah default None:
def sapa(nama: str | None = None) -> str:
if nama is None:
return "Halo, tamu!"
return f"Halo, {nama}!"
print(sapa())
print(sapa("Siti"))Output yang Dihasilkan:
Halo, tamu!
Halo, Siti!Perbandingan nama is None sekaligus menjadi penyempit tipe: setelah cabang None ditangani, checker memperlakukan nama sebagai str murni di cabang berikutnya.
Implicit Optional Ditolak mypy #
Kebiasaan lama Python mengizinkan penulisan nama: str = None dengan harapan checker membacanya str | None. PEP 484 melarang bentuk implicit Optional ini, dan mypy kini menolaknya secara bawaan (no_implicit_optional=True). Simpan sebagai opsional.py:
def sapa(nama: str = None) -> str:
if nama is None:
return "Halo, tamu!"
return f"Halo, {nama}!"
print(sapa())python3 -m mypy opsional.pyOutput yang Dihasilkan:
opsional.py:1: error: Incompatible default for parameter "nama" (default has type "None", parameter has type "str") [assignment]
opsional.py:1: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
opsional.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)Perbaikannya: tuliskan union secara eksplisit menjadi nama: str | None = None, persis seperti contoh sapa() di atas.
Lupa Menangani None #
Kekuatan union terlihat saat kita lalai. Simpan sebagai nama_user.py:
def cari_user(user_id: int) -> str | None:
database = {1: "Budi", 2: "Siti"}
return database.get(user_id)
nama = cari_user(99)
print(f"Halo, {nama.upper()}!")python3 -m mypy nama_user.pyOutput yang Dihasilkan:
nama_user.py:6: error: Item "None" of "str | None" has no attribute "upper" [union-attr]
Found 1 error in 1 file (checked 1 source file)mypy menunjuk baris ke-6: variabel nama bertipe str | None, dan None tidak memiliki method upper(). Pesan ini akan benar-benar terjadi di runtime — cari_user(99) mengembalikan None, lalu AttributeError menghentikan program. Anotasi union membuat kesalahan semacam ini tertangkap sebelum eksekusi.
isinstance dan Union di Runtime #
Di sisi runtime, isinstance() sejak Python 3.10 juga menerima objek union sebagai argumen keduanya:
>>> isinstance(3.5, int | float)
True
>>> isinstance("kopi", int | float)
FalsePemeriksaan isinstance di badan function dengan sintaks yang sama persis dengan anotasinya — tidak ada tuple isinstance(x, (int, float)) terpisah yang harus dijaga sinkron. Satu ekspresi int | float berfungsi ganda: sebagai anotasi untuk checker dan sebagai objek pemeriksaan untuk runtime.