Pipeline besar dipecah menjadi puluhan modul, dan data mengalir melewati banyak tangan. Tanpa deklarasi tipe, satu bentuk yang salah — misalnya mengoper dict padahal fungsi mengharapkan list — baru muncul saat program berjalan, kadang di job produksi tengah malam. Type hints menuliskan kontrak tersebut di dalam kode: parameter dan nilai kembali diberi anotasi tipe, lalu mypy memeriksa seluruh berkas tanpa mengeksekusinya. Chapter ini menerapkan anotasi pada fungsi transform, membedah Optional dan Any, lalu membuktikan mypy menangkap galat yang luput dari eksekusi biasa.

Anotasi Dasar pada Fungsi #

Anotasi memakai tanda titik dua untuk parameter dan tanda panah untuk nilai kembali: def bersih_harga(nilai: str) -> int: berarti teks masuk, bilangan bulat keluar. Variabel lokal juga bisa dianotasi, misalnya hasil: list[dict[str, Any]] = [].

Satu hal yang wajib dipahami: Python tidak menegakkan anotasi saat runtime — mengoper "tiga" ke parameter int tetap berjalan sampai operasi aritmetikanya gagal. Penegakan adalah pekerjaan pemeriksa statis; anotasi adalah bahan bakarnya.

Optional, Any, dan Bentuk list[dict[str, Any]] #

Optional[dict[str, Any]] dibaca sebagai “dict[str, Any], atau None” — dipakai ketika None adalah jawaban sah, misalnya fungsi pencarian. Sejak Python 3.10, dict[str, Any] | None berarti sama; mypy mengerti keduanya.

Any adalah pintu darurat: tipe apa pun diterima. Pada record hasil API yang kolomnya berubah-ubah, Any masuk akal untuk bagian yang tidak kita kendalikan. Gunakan Any sesempit mungkin; list[dict[str, Any]] tetap jauh lebih informatif daripada tanpa anotasi. Rujukan lengkap: https://docs.python.org/3/library/typing.html .

Contoh Teruji: Fungsi transform Beranotasi #

Blok berikut menghimpun anotasi dasar, Optional, Any, dan fungsi generik TypeVar dalam satu skrip yang berjalan:

python
from typing import Any, Optional, TypeVar

T = TypeVar("T")


def bersih_harga(nilai: str) -> int:
    """Ubah 'Rp 24.000' menjadi 24000."""
    angka = "".join(karakter for karakter in nilai if karakter.isdigit())
    return int(angka) if angka else 0


def transform(baris: list[dict[str, Any]]) -> list[dict[str, Any]]:
    """Tambahkan kolom total pada setiap record."""
    hasil: list[dict[str, Any]] = []
    for record in baris:
        hasil.append({
            "id": record["id"],
            "produk": str(record["produk"]).title(),
            "total": bersih_harga(record["harga"]) * int(record["qty"]),
        })
    return hasil


def cari_record(baris: list[dict[str, Any]], id_trx: str) -> Optional[dict[str, Any]]:
    """Kembalikan record yang cocok, atau None bila tidak ditemukan."""
    for record in baris:
        if record["id"] == id_trx:
            return record
    return None


def pertama(items: list[T]) -> Optional[T]:
    """Fungsi generik: elemen pertama list, atau None bila kosong."""
    return items[0] if items else None


penjualan = [
    {"id": "TRX-001", "produk": "kopi lampung", "qty": "2", "harga": "Rp 24.000"},
    {"id": "TRX-002", "produk": "teh melati", "qty": "3", "harga": "Rp15.000"},
    {"id": "TRX-003", "produk": "gula aren", "qty": "1", "harga": "Rp27.000"},
]

hasil = transform(penjualan)
print(hasil)

tertinggi = max(hasil, key=lambda r: r["total"])
print("Tertinggi:", tertinggi["id"], "Rp" + str(tertinggi["total"]))

ditemukan = cari_record(penjualan, "TRX-003")
if ditemukan:
    print("TRX-003 ditemukan:", ditemukan["produk"])
print("TRX-999:", cari_record(penjualan, "TRX-999"))

atas = pertama(hasil)
if atas:
    print("Record pertama:", atas["id"])
print("pertama([]):", pertama([]))
bash
python3 transform_annotasi.py

Output yang Dihasilkan:

text
[{'id': 'TRX-001', 'produk': 'Kopi Lampung', 'total': 48000}, {'id': 'TRX-002', 'produk': 'Teh Melati', 'total': 45000}, {'id': 'TRX-003', 'produk': 'Gula Aren', 'total': 27000}]
Tertinggi: TRX-001 Rp48000
TRX-003 ditemukan: gula aren
TRX-999: None
Record pertama: TRX-001
pertama([]): None

Mari kita telaah baris-per-baris kode di atas:

  1. Baris 6-9 (bersih_harga): kontraknya sempit — teks masuk, bilangan bulat keluar; pemanggil tidak perlu membaca badan fungsi.
  2. Baris 12-14 (transform): list[dict[str, Any]] dibaca dari luar ke dalam: list berisi dict, kunci string, nilai beragam; hasil dianotasi sama.
  3. Baris 24-29 (cari_record): keluaran Optional memaksa pemanggil mengurus None; if ditemukan: (baris 50-51) menyempitkan tipe menjadi dict, jadi akses ditemukan["produk"] setelahnya dianggap aman.
  4. Baris 32-34 (pertama): TypeVar("T") membuat fungsi generik: pertama(hasil) menghasilkan Optional[dict[str, Any]], pertama(["a"]) menghasilkan Optional[str].

Static Type Checking dengan mypy #

mypy memeriksa seluruh berkas tanpa menjalankannya. Berkas berikut sengaja berisi galat tipe; sintaksnya sah dan lolos py_compile:

python
def total(qty: int, harga: int) -> int:
    return qty * harga


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


# Berkas ini sengaja berisi galat tipe.
# Periksa dengan mypy; jangan dijalankan dengan python.
subtotal = total("2", 24000)
label = 20000 + "Rp"
sapa(None)

Saat chapter ini ditulis, berkas tersebut diperiksa dengan mypy 2.4.0 dan hasilnya tiga galat. Baris 11: Argument 1 to "total" has incompatible type "str"; expected "int" — argumen "2" tidak cocok dengan parameter qty: int. Baris 12: Unsupported operand types for + ("int" and "str"). Baris 13: Argument 1 to "sapa" has incompatible type "None"; expected "str" — None dikirim ke parameter nama: str.

Ketiganya berbeda nasib pada runtime: baris 11 dan 13 tetap berjalan dengan nilai salah jenis ("2" * 24000 menghasilkan string), baris 12 baru berhenti sebagai TypeError. mypy menangkap semuanya sebelum satu baris kode pun dieksekusi. Dokumentasi mypy: https://mypy.readthedocs.io/ .

Mengapa Type Hints Penting di Pipeline Besar #

  • Kontrak antar modul terlihat: pemanggil cukup membaca anotasi untuk tahu bentuk data yang diminta.
  • Refactoring jadi aman: mengubah tipe keluaran satu fungsi langsung ketahuan di semua pemanggilnya.
  • Editor ikut bekerja: pelengkap otomatis menawarkan kolom yang benar saat menulis.
  • Onboarding lebih cepat: anggota tim baru langsung paham bentuk data yang mengalir.

Anotasi tidak menggantikan pengujian — pengujian membuktikan perilaku, anotasi menjaga bentuk. Keduanya paling terasa ketika data besar tidak boleh dimuat sekaligus, melainkan harus mengalir sedikit demi sedikit. Alatnya adalah iterator dan generator, topik chapter berikutnya, Iterators dan Generators.