Kadang satu function menjual dua perilaku sekaligus: kirim int, dapat int; kirim str, dapat str. Union menyelesaikan sisi masukan, tetapi sisi keluaran jadi kabur — nilai balik int | str memaksa pemanggil mengecek dua-duanya, bahkan ketika hubungan masukan-keluaran sebenarnya pasti. Dekorator @overload memungkinkan kita menuliskan beberapa signature sekaligus untuk satu function, sehingga checker memahami setiap kombinasi secara presisi.

Masalah: Union Masuk, Union Keluar #

Perhatikan function kali berikut:

python
def kali(a: int | str, b: int) -> int | str:
    if isinstance(a, str):
        return a * b
    return a * b

print(kali(3, 4))
print(kali("kopi-", 2))

Output yang Dihasilkan:

text
12
kopi-kopi-

Signature tersebut jujur, tetapi terlalu longgar. Pemanggil yang mengirim str tahu hasilnya pasti str — a * b dengan a string pasti menghasilkan string — namun checker hanya melihat int | str dan ikut bersikap defensif. Simpan sebagai tanpa_overload.py:

python
def kali(a: int | str, b: int) -> int | str:
    if isinstance(a, str):
        return a * b
    return a * b

hasil = kali("kopi-", 2)
print(hasil.upper())
bash
python3 -m mypy tanpa_overload.py

Output yang Dihasilkan:

text
tanpa_overload.py:7: error: Item "int" of "int | str" has no attribute "upper"  [union-attr]
Found 1 error in 1 file (checked 1 source file)

mypy menolak .upper() karena menurut signature, hasil bisa saja int. Padahal setiap pembaca manusia tahu baris tersebut aman: masukan str selalu menghasilkan str. Signature union kehilangan informasi hubungan antar-parameter seperti yang kita pertahankan lewat generics pada Chapter Generic Types.

Solusinya: daftarkan setiap kombinasi sebagai signature @overload, lalu tutup dengan satu implementasi yang menyatukan semuanya:

python
from typing import overload

@overload
def kali(a: int, b: int) -> int: ...
@overload
def kali(a: str, b: int) -> str: ...

def kali(a: int | str, b: int) -> int | str:
    if isinstance(a, str):
        return a * b
    return a * b

print(kali(3, 4))
print(kali("kopi-", 2))

Output yang Dihasilkan:

text
12
kopi-kopi-

Mari kita telaah strukturnya:

  1. Baris 3-4 (@overload pertama): deklarasi “jika a bertipe int dan b bertipe int, nilai balik int”. Badannya hanya ... — ini spesifikasi, bukan implementasi.
  2. Baris 5-6 (@overload kedua): deklarasi “jika a bertipe str, nilai balik str”. Checker mencocokkan pemanggilan ke deklarasi yang paling pas, berurutan dari atas.
  3. Baris 8 (implementasi): satu function tanpa dekorator yang menampung semua kasus — signature-nya harus menerima setiap kombinasi dari deklarasi di atasnya. Hanya function inilah yang benar-benar berjalan di runtime; seluruh baris @overload di atasnya diabaikan interpreter.

Sekarang checker memiliki peta yang presisi. Uji kembali kasus yang sebelumnya ditolak — simpan sebagai dengan_overload.py:

python
from typing import overload

@overload
def kali(a: int, b: int) -> int: ...
@overload
def kali(a: str, b: int) -> str: ...

def kali(a: int | str, b: int) -> int | str:
    if isinstance(a, str):
        return a * b
    return a * b

hasil = kali("kopi-", 2)
print(hasil.upper())
bash
python3 -m mypy dengan_overload.py

Output yang Dihasilkan:

text
Success: no issues found in 1 source file

Kode .upper() yang sama, kini diterima: checker mencocokkan kali("kopi-", 2) dengan deklarasi kedua, menyimpulkan hasil pasti str, dan meloloskan pemanggilan .upper() tanpa penyempitan manual.

Kapan Memakai overloads #

Pola umum yang layak diberi @overload:

  • Union masuk menentukan tipe keluar — contoh kali di atas.
  • Parameter default None — misalnya cari(x: str) -> str dan cari() -> None, dua perilaku berbeda dari satu nama.
  • Builder berantai — method yang mengembalikan Self atau tipe spesifik sesuai flag.

Jika satu function hanya punya satu perilaku, jangan buat overload — satu signature biasa sudah cukup. Overload adalah alat untuk kejujuran tipe, bukan gaya penulisan.

Konsep Stub File #

Pattern “deklarasi tanpa badan” pada @overload punya saudara besar di tingkat berkas: stub file berekstensi .pyi. Sebuah stub berisi signature dan anotasi untuk seluruh module — tanpa satu baris logika pun. Checker memakai stub tanpa membuka file .py aslinya.

Kita sudah mengalami manfaatnya secara diam-diam: mypy memeriksa len(), list.append(), dan seluruh standard library bukan dengan membaca kode C implementasinya, melainkan lewat Typeshed — kumpulan stub resmi yang dibundel mypy. Ketika sebuah library pihak ketiga tidak menyertakan anotasi, komunitas menyediakan package *-stubs dengan mekanisme yang sama. Konsepnya sama dengan @overload: pisahkan kontrak tipe dari implementasi.