Coba tulis function yang mengambil elemen pertama sebuah list. Dengan list[int] sebagai parameter, function tersebut menolak list of string; dengan list[Any], function menerima semuanya tetapi checker kehilangan jejak tipe — nilai baliknya Any, dan kesalahan setelahnya tidak akan pernah tertangkap. Yang kita butuhkan adalah cara mengatakan: “parameter dan nilai balik bertipe yang sama, apa pun tipe itu”. Inilah pekerjaan type variable — dasar dari generics.
TypeVar dan Function Generic #
Type variable didefinisikan dengan TypeVar dari module typing, lalu dipakai sebagai parameter anotasi layaknya tipe biasa:
from typing import TypeVar
T = TypeVar("T")
def ambil_pertama(items: list[T]) -> T:
return items[0]
print(ambil_pertama([1, 2, 3]))
print(ambil_pertama(["kopi", "teh"]))Output yang Dihasilkan:
1
kopiMari kita telaah cara kerjanya:
- Baris 3 (
T = TypeVar("T")): variabelTmenjadi tempat penampung tipe — bukan tipe konkret, melainkan “tipe yang akan ditentukan pemanggil”. - Baris 5 (
items: list[T]) dan-> T: kedua posisi memakaiTyang sama, sehingga checker menerapkan aturan “sekali T ditentukan, semua kemunculannya ikut”. - Pemanggilan
ambil_pertama([1, 2, 3]): mypy menyimpulkanT = intuntuk pemanggilan ini — nilai balik berstatusint. - Pemanggilan
ambil_pertama(["kopi", "teh"]):Tdisimpulkanstr— function yang sama, kontrak tipe yang berbeda sesuai pemanggil.
Inilah perbedaan mendasar dengan Any: generic mempertahankan hubungan antar-posisi. Nilai yang masuk sebagai str keluar sebagai str, dan checker tetap mengawasinya.
Class Generic dengan Generic[T] #
Mekanisme yang sama bekerja untuk class melalui pewarisan Generic[T]:
from typing import Generic, TypeVar
T = TypeVar("T")
class Kotak(Generic[T]):
def __init__(self, isi: T) -> None:
self.isi = isi
def ambil(self) -> T:
return self.isi
kotak_angka = Kotak(42)
kotak_nama = Kotak("Budi")
print(kotak_angka.ambil() + 8)
print(kotak_nama.ambil().upper())Output yang Dihasilkan:
50
BUDISaat Kotak(42) dibuat, checker mengunci T = int untuk object kotak_angka — method .ambil() milik kotak_angka pasti mengembalikan int, sehingga + 8 aman. Untuk kotak_nama, T = str, sehingga .upper() sah. Tidak ada satu pun pemeriksaan runtime di kode ini; seluruh jaminan dikerjakan checker saat membaca kode.
Membatasi TypeVar dengan bound= #
TypeVar tanpa batas menerima tipe apa pun — termasuk tipe yang tidak punya method yang kita butuhkan. Misalkan function bersuara() memanggil hewan.suara(); kita perlu memastikan T selalu punya method tersebut. Solusinya: bound=:
from typing import TypeVar
class Hewan:
def suara(self) -> str:
return "..."
class Kucing(Hewan):
def suara(self) -> str:
return "Meong"
H = TypeVar("H", bound=Hewan)
def bersuara(hewan: H) -> str:
return hewan.suara()
print(bersuara(Kucing()))Output yang Dihasilkan:
MeongTypeVar H dibatasi pada Hewan dan seluruh subclass-nya, sehingga checker menjamin method .suara() selalu tersedia di dalam badan bersuara. Keuntungan memakai generic (dibanding sekadar hewan: Hewan) adalah tipe asli terjaga: jika kelak Kucing punya method .lompat(), nilai balik dan penelusuran tipe tidak “menyusut” menjadi Hewan.
Constrained TypeVar: Pilihan Tertutup #
Bentuk kedua pembatasan adalah constrained — TypeVar yang hanya menerima beberapa tipe pilihan:
from typing import TypeVar
Angka = TypeVar("Angka", int, float)
def dua_kali(nilai: Angka) -> Angka:
return nilai * 2
print(dua_kali(21))
print(dua_kali(3.5))Output yang Dihasilkan:
42
7.0TypeVar Angka hanya boleh diisi int atau float — tidak lebih. Checker menolak tipe lain. Simpan sebagai angka.py:
from typing import TypeVar
Angka = TypeVar("Angka", int, float)
def dua_kali(nilai: Angka) -> Angka:
return nilai * 2
print(dua_kali("kopi"))python3 -m mypy angka.pyOutput yang Dihasilkan:
angka.py:8: error: Value of type variable "Angka" of "dua_kali" cannot be "str" [type-var]
Found 1 error in 1 file (checked 1 source file)Kapan Generic, Kapan Any #
| Situasi | Pilihan tepat | Alasan |
|---|---|---|
| Tipe masuk dan keluar harus sama (identitas terjaga) | TypeVar | Checker menjaga hubungan antar-posisi |
| Tipe dibatasi pada satu keluarga class | bound= | Method dasar dijamin tersedia |
| Hanya beberapa tipe konkret sah | constrained TypeVar | Daftar putih tipe ditegakkan |
| Benar-benar bebas tipe dan tidak diperiksa | Any | Jujur menyatakan “bagian ini tak diperiksa” |
| Lupa masih tahap prototipe | tanpa anotasi | Sama dengan Any menurut gradual typing |
Any bukan musuh — Any adalah katup yang jujur untuk kode lama dan batas library. Yang perlu dihindari adalah memakai Any di tempat yang sebenarnya bisa dinyatakan dengan generic, karena setiap Any yang bisa dihapus membuat checker melihat lebih jauh.