Pada Chapter Type Hints pada Fungsi di Section Fungsi, kita sudah menulis anotasi dasar seperti nama: str dan -> float, dan kita tahu interpreter Python tidak menegakkannya saat runtime. Section ini memperluas bekal tersebut menjadi sistem tipe yang utuh: koleksi generik, nilai kosong, Callable, generics, hingga Protocol. Sebelum masuk ke sintaks baru, kita perlu memahami tiga hal terlebih dahulu: mengapa kode besar membutuhkan anotasi, bagaimana filosofi gradual typing bekerja, dan cara berpikir dari sudut pandang static type checker. Sepanjang Section ini kita memakai mypy 2.4.0 sebagai checker rujukan; seluruh contoh keluarannya diambil dari eksekusi nyata.

Mengapa Kode Besar Membutuhkan Anotasi #

Program 50 baris bisa digenggam dalam kepala; kita masih ingat bahwa items berisi list of string. Ketika project tumbuh menjadi puluhan module dan ratusan function, konteks itu hilang — bahkan bagi kode yang kita tulis sendiri tiga bulan lalu. Type hints mengembalikan konteks tersebut ke tempat yang paling berguna: signature.

Manfaat konkret anotasi pada codebase besar:

  • Dokumentasi yang selalu sinkron — signature menjelaskan parameter dan nilai balik tanpa perlu membuka badan function; dokumentasi terpisah mudah kedaluwarsa, anotasi tidak.
  • Editor menjadi lebih pintar — code editor membaca anotasi lalu menyediakan pelengkapan otomatis yang akurat dan menandai argument yang salah tipe saat kita mengetik.
  • Refactor menjadi aman — saat kita mengubah tipe nilai balik sebuah function, editor dan checker langsung menunjuk semua pemanggil yang ikut rusak.
  • Pemeriksaan otomatis — alat seperti mypy dan Pyright memeriksa seluruh file tanpa menjalankan program; kesalahan tertangkap jauh sebelum sampai ke pengguna.

Inti manfaatnya satu: type hints memindahkan beban “mengingat detail tipe” dari kepala programmer ke alat bantu.

Anotasi adalah Kontrak, Bukan Penjaga #

Kita telah melihat pada Chapter Type Hints pada Fungsi bahwa interpreter Python hanya menyimpan anotasi di atribut __annotations__ lalu mengabaikannya saat eksekusi. Konsekuensinya: program tetap berjalan meskipun kita melanggar kontrak anotasi sendiri. Penegakan aturan jatuh ke tangan alat eksternal. Simpan kode berikut sebagai sapa.py:

python
def sapa(nama: str) -> str:
    return f"Halo, {nama}!"

print(sapa(42))

Dijalankan dengan python3 sapa.py, program tetap mencetak Halo, 42! — f-string menerima integer 42 tanpa keluhan. Sekarang jalankan mypy pada berkas yang sama:

bash
python3 -m mypy sapa.py

Output yang Dihasilkan:

text
sapa.py:4: error: Argument 1 to "sapa" has incompatible type "int"; expected "str"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

mypy membaca berkas baris demi baris tanpa mengeksekusinya, lalu melaporkan bahwa argument pertama untuk sapa bertipe int padahal deklarasinya str. Kode galat [arg-type] mengategorikan jenis pelanggaran — kategori ini memudahkan kita memfilter laporan saat codebase membesar. Inilah pembagian kerja yang perlu diingat sepanjang Section ini: runtime menjalankan kode, checker membacanya.

Gradual Typing: Mengetik Sedikit demi Sedikit #

Kode Python lama di dunia tidak memiliki anotasi, dan memaksa “semua atau tidak sama sekali” akan membuat type hints tidak pernah diadopsi. Karena itu Python memilih gradual typing: kode beranotasi dan tanpa anotasi boleh hidup berdampingan dalam satu project.

Mekanismenya sederhana. Bagian tanpa anotasi diperlakukan sebagai Any — tipe khusus yang kompatibel dengan semua tipe lain, sehingga checker diam saja melewatkannya. Bagian beranotasi diperiksa penuh. Akibatnya kita bisa memulai dari function yang paling kritis (misalnya lapisan domain dan API publik), lalu menyebar anotasi bertahap ke tepi project tanpa harus menulis ulang semuanya sekaligus.

Bertanya kepada Checker dengan reveal_type #

Saat belajar sistem tipe, kita sering ingin tahu: “tipe apa yang checker simpulkan untuk ekspresi ini?” mypy menyediakan pseudo-function reveal_type() khusus untuk itu. Simpan sebagai reveal.py:

python
nama = "Budi"
umur = 17
reveal_type(nama)
reveal_type(umur)

Jalankan mypy (jangan python3 — reveal_type bukan function bawaan runtime):

bash
python3 -m mypy reveal.py

Output yang Dihasilkan:

text
reveal.py:3: note: Revealed type is "str"
reveal.py:4: note: Revealed type is "int"
Success: no issues found in 1 source file

Dua baris note menunjukkan hasil inferensi mypy: literal "Budi" disimpulkan str dan 17 disimpulkan int. Kita akan sering memakai reveal_type() pada chapter-chapter berikutnya untuk melihat bagaimana checker memahami generics, Optional, dan Callable. Ingat: panggilan reveal_type() adalah alat debugging — hapus sebelum kode dikirim.

Dengan motivasi, pembagian kerja runtime/checker, gradual typing, dan reveal_type() di tangan, kita siap menyusuri sistem tipe Python mulai dari koleksi yang paling sering kita pakai setiap hari.