Modul enum mengubah kumpulan konstanta menjadi tipe dengan anggota bernama, sedangkan modul typing menyediakan kosakata anotasi untuk menggambarkan tipe data kepada pembaca dan alat pemeriksa statis. Keduanya tidak menambah kecepatan program, tetapi keduanya menaikkan kejelasan kode, karena maksud data tertulis secara eksplisit dan diverifikasi sebelum program dijalankan. Topik ini membahas Enum beserta auto, anotasi Optional dan Union dengan sintaks pipa, Protocol untuk pengetikan struktural, TypedDict untuk dictionary terstruktur, serta program kecil yang memadukannya.
Enum untuk konstanta bernama#
Kelas yang mewarisi Enum mendefinisikan anggota (member) dengan nama dan nilai. Setiap anggota berupa objek tunggal, sehingga perbandingan antar anggota berlangsung lewat identitas, dan anggota dapat dicari dari nilai maupun dari namanya.
from enum import Enum
class Status(Enum):
MENUNGGU = 1
BERJALAN = 2
SELESAI = 3
tugas = Status.BERJALAN
print(tugas)
print(tugas.name, tugas.value)
print(tugas is Status.BERJALAN)
print(Status(2))
print(Status["SELESAI"])
print([s.name for s in Status])Status.BERJALAN
BERJALAN 2
True
Status.BERJALAN
Status.SELESAI
['MENUNGGU', 'BERJALAN', 'SELESAI']Pencetakan tugas menampilkan Status.BERJALAN, yaitu repr yang menyebut kelas dan nama anggotanya, bukan nilai mentahnya. Pencarian Status(2) bekerja dari nilai ke anggota, sedangkan Status["SELESAI"] bekerja dari nama ke anggota. Iterasi atas kelas menghasilkan seluruh anggota menurut urutan definisinya, sehingga menu seperti daftar pilihan dapat dibangun langsung dari enum tersebut.
Nilai otomatis dengan auto#
Fungsi auto() menetapkan nilai anggota secara otomatis, dimulai dari 1 dan bertambah satu setiap pemanggilan. Nilai yang dihasilkan sengaja dianggap tidak penting bagi pemakai enum, sehingga definisi tetap ringkas ketika nilai mentah memang tidak dipakai untuk apa pun.
from enum import Enum, auto
class Arah(Enum):
UTARA = auto()
SELATAN = auto()
TIMUR = auto()
BARAT = auto()
print([a.value for a in Arah])
print(Arah(2).name)[1, 2, 3, 4]
SELATANPencarian Arah(2) tetap berfungsi karena nilai otomatis itu tetap berupa angka berurutan. Apabila nilai enum perlu dibandingkan dengan teks pada data luar, pustaka enum menyediakan turunan seperti StrEnum yang anggotanya berupa string. Untuk penggunaan di dalam program, nilai otomatis sudah cukup dan menghindari angka ajaib, yaitu angka yang ditulis berulang tanpa penjelasan.
Optional, Union, dan sintaks pipa#
Anotasi Optional[X] menyatakan nilai bertipe X atau None, sedangkan Union[X, Y] menyatakan pilihan di antara beberapa tipe. Sejak Python 3.10, keduanya dapat ditulis dengan operator pipa, yaitu X | None dan X | Y, dan bentuk pipa inilah yang lazim pada kode modern.
def panjang_atau_nol(teks: str | None) -> int:
if teks is None:
return 0
return len(teks)
def ringkas(nilai: int | str) -> str:
if isinstance(nilai, int):
return "angka " + str(nilai)
return "teks " + repr(nilai)
kamus = {"a": "apel"}
print(panjang_atau_nol(kamus.get("a")))
print(panjang_atau_nol(kamus.get("z")))
print(panjang_atau_nol(None))
print(ringkas(7), ringkas("tujuh"))4
0
0
angka 7 teks 'tujuh'Kunci z yang tidak ada membuat kamus.get("z") mengembalikan None, dan fungsi yang dianotasi str | None menangani kemungkinan itu secara eksplisit alih-alih menimbulkan TypeError. Anotasi int | str pada ringkas menggambarkan pola yang sama untuk pilihan tipe, dan cabang isinstance di dalamnya menjadi penentu jalannya.
Protocol dan TypedDict#
Kelas Protocol menetapkan kontrak metode tanpa pewarisan, sehingga objek apa pun yang memiliki metode yang sesuai dianggap memenuhi protokolnya. Pendekatan ini memformalkan duck typing menjadi sesuatu yang dapat diperiksa alat statis. Sementara itu, TypedDict menggambarkan dictionary dengan kunci dan tipe nilai yang ditetapkan, tetapi tetap berupa dictionary biasa saat dijalankan.
from typing import Protocol, TypedDict, runtime_checkable
@runtime_checkable
class Penyimpan(Protocol):
def simpan(self, kunci: str, nilai: str) -> None: ...
class Memori:
def simpan(self, kunci: str, nilai: str) -> None:
print("menyimpan", kunci, "=", nilai, "di memori")
class Berkas:
def simpan(self, kunci: str, nilai: str) -> None:
print("menyimpan", kunci, "=", nilai, "di berkas")
def tulis(wadah: Penyimpan) -> None:
wadah.simpan("status", "aktif")
tulis(Memori())
tulis(Berkas())
print(isinstance(Memori(), Penyimpan))menyimpan status = aktif di memori
menyimpan status = aktif di berkas
TrueKedua kelas tidak mewarisi apa pun, tetapi keduanya memenuhi Penyimpan karena memiliki metode simpan dengan tanda tangan yang sesuai. Decorator runtime_checkable membuat protokol dapat dipakai pada isinstance, yang hanya memeriksa keberadaan metodenya. Untuk data, TypedDict mendefinisikan bentuk dictionary secara bernama.
from typing import TypedDict
class Pengguna(TypedDict):
nama: str
umur: int
data: Pengguna = {"nama": "Dewi", "umur": 24}
print(data["nama"], data["umur"])
print(Pengguna.__annotations__)Dewi 24
{'nama': <class 'str'>, 'umur': <class 'int'>}Nilai data tetap berupa dict biasa dan tidak ada pemeriksaan saat dijalankan, tetapi alat statis akan menolak kunci yang tidak dikenal atau nilai yang bertipe keliru. Kunci opsional dinyatakan melalui warisan total=False, sehingga bentuk dictionary yang longgar tetap terdokumentasi.
Program utuh: daftar tugas bertipe#
Contoh berikut memadukan Enum, Optional, dan TypedDict pada satu program kecil, yaitu daftar tugas dengan status bernama dan catatan yang boleh kosong.
from enum import Enum, auto
from typing import Optional, TypedDict
class Status(Enum):
MENUNGGU = auto()
BERJALAN = auto()
SELESAI = auto()
class Tugas(TypedDict):
judul: str
status: Status
catatan: Optional[str]
def ringkas(tugas: Tugas) -> str:
baris = f"{tugas['judul']}: {tugas['status'].name}"
if tugas["catatan"] is not None:
baris += f" ({tugas['catatan']})"
return baris
daftar = [
Tugas(judul="cadangkan arsip", status=Status.SELESAI, catatan=None),
Tugas(judul="perbarui laporan", status=Status.BERJALAN, catatan="menunggu data"),
]
for tugas in daftar:
print(ringkas(tugas))
print("nilai enum:", tugas["status"].value)cadangkan arsip: SELESAI
nilai enum: 3
perbarui laporan: BERJALAN (menunggu data)
nilai enum: 2Simpan berkas di atas di direktori kerja, lalu jalankan dengan perintah berikut.
# menjalankan program daftar tugas bertipe
python3 status_tugas.pycadangkan arsip: SELESAI
nilai enum: 3
perbarui laporan: BERJALAN (menunggu data)
nilai enum: 2Tipe Tugas menggambarkan bentuk datanya, sedangkan Status menggambarkan himpunan nilai yang sah untuk kolom statusnya. Nilai catatan yang dianotasi Optional[str] dibedakan dengan None sebelum dipakai, sehingga baris ringkasan menyesuaikan diri tanpa mencetak None mentah.
Kesalahan umum#
Kesalahan pertama adalah membandingkan anggota enum dengan nilai mentahnya. Anggota Enum bukan angka maupun string, sehingga perbandingan langsung dengan nilai mentah menghasilkan False meskipun nilainya sama.
from enum import Enum
class Status(Enum):
BERJALAN = 2
print(Status.BERJALAN == 2)
print(Status.BERJALAN == Status(2))False
TruePerbandingan yang benar dilakukan antar anggota, baik langsung seperti Status.BERJALAN maupun melalui konversi Status(2) dari nilai mentah yang datang dari luar program.
Kesalahan kedua adalah menganggap anotasi tipe sebagai penjaga runtime. Fungsi yang dianotasi int tetap menerima argumen lain dan menjalankannya seadanya.
def dobel(x: int) -> int:
return x * 2
print(dobel("ab"))ababFungsi itu menggandakan string tanpa error karena * sah untuk str, dan error hanya muncul bila operasinya memang tidak didukung tipe itu. Pemeriksaan tipe menempati ranah alat statis, sehingga penemuan dobel("ab") yang tidak disengaja menjadi tugas mypy atau pyright, bukan interpreter.
Kesalahan ketiga adalah memakai isinstance pada Protocol yang tidak didekorasi runtime_checkable. Pemeriksaan itu langsung menimbulkan error, sehingga objeknya tidak turut diperiksa.
from typing import Protocol
class Diurut(Protocol):
def __lt__(self, lain): ...
try:
isinstance(3, Diurut)
except TypeError as e:
print(type(e).__name__, str(e))TypeError Instance and class checks can only be used with @runtime_checkable protocolsPerbaikannya adalah menambahkan decorator @runtime_checkable pada protokol yang memang perlu diuji saat berjalan, dengan catatan pemeriksaannya hanya menilai keberadaan nama metode, bukan tanda tangannya.
Ringkasan#
- Kelas
Enummendefinisikan anggota bernama yang dibandingkan lewat identitas, dapat dicari dari nilai dan dari nama, serta mendukung nilai otomatis melaluiauto(). - Anotasi
Optional[X]danUnionditulis modern sebagaiX | NonedanX | Y, dan seluruh anotasi diperiksa oleh alat statis, bukan interpreter. Protocolmenetapkan kontrak metode tanpa pewarisan, dengan@runtime_checkablemengizinkan pemeriksaanisinstanceyang bersifat nama-metode saja.TypedDictmenggambarkan dictionary dengan kunci dan tipe tetap, tetapi objeknya tetap berupa dictionary biasa saat program berjalan.
Referensi#
- enum — Support for enumerations — Python Software Foundation — Referensi modul enum beserta auto, pencarian anggota, dan turunannya.
- typing — Support for type hints — Python Software Foundation — Referensi Optional, Union, Protocol, dan TypedDict.
- PEP 544 — Protocols: Structural subtyping — Python Software Foundation — Dasar pengetikan struktural dan aturan runtime_checkable.