Status pesanan adalah contoh klasik data dengan himpunan nilai tetap: baru, dibayar, dikirim, selesai. Jika nilai-nilai tersebut ditulis sebagai string atau angka lepas di seluruh kode, satu typo — "dibayr" — lolos tanpa galat dan berubah menjadi bug logika yang senyap. Module enum menyediakan class Enum untuk mengelompokkan konstanta bernama dalam satu tempat, menjadikannya satu-satunya sumber kebenaran bagi himpunan nilai tersebut.

Enumerasi Dasar dengan class Enum #

Sebuah Enum didefinisikan sebagai class yang mewarisi Enum; setiap anggota class body menjadi member enumerasi. Setiap member adalah objek tunggal (singleton): mengakses StatusPesanan.DIBAYAR selalu mengembalikan objek yang sama persis.

python
>>> from enum import Enum
>>> class StatusPesanan(Enum):
...     BARU = "baru"
...     DIBAYAR = "dibayar"
...     DIKIRIM = "dikirim"
...     SELESAI = "selesai"
... 
>>> StatusPesanan.DIBAYAR
<StatusPesanan.DIBAYAR: 'dibayar'>
>>> StatusPesanan.DIBAYAR.name
'DIBAYAR'
>>> StatusPesanan.DIBAYAR.value
'dibayar'

Setiap member memiliki dua sisi: atribut name (identifier dalam class body) dan atribut value (nilai yang kita tetapkan). Keduanya berbeda dari objek member itu sendiri, dan representasi <StatusPesanan.DIBAYAR: 'dibayar'> menampilkan keduanya. Bandingkan dengan konstanta biasa: StatusPesanan.DIBAYAR membawa nama, nilai, dan tipe dalam satu objek, bukan sekadar string telanjang.

Lookup dua arah juga tersedia — member bisa dicari berdasarkan nilai:

python
from enum import Enum


class StatusPesanan(Enum):
    BARU = "baru"
    DIBAYAR = "dibayar"
    DIKIRIM = "dikirim"
    SELESAI = "selesai"


status = StatusPesanan.DIBAYAR

print(status)
print(status.name)
print(status.value)
print(status is StatusPesanan.DIBAYAR)

# pencarian anggota berdasarkan nilai
print(StatusPesanan("selesai"))

# nilai yang tidak terdaftar ditolak sejak awal
try:
    StatusPesanan("batal")
except ValueError as e:
    print("ValueError:", e)

Output yang Dihasilkan:

text
StatusPesanan.DIBAYAR
DIBAYAR
dibayar
True
StatusPesanan.SELESAI
ValueError: 'batal' is not a valid StatusPesanan

Perhatikan baris print(StatusPesanan("selesai")): pencarian berdasarkan nilai mengembalikan member StatusPesanan.SELESAI yang utuh. Sebaliknya, nilai "batal" yang tidak terdaftar langsung ditolak dengan ValueError — galat yang muncul di tempat kesalahan terjadi, bukan bug senyap tiga modul kemudian. Karena setiap member adalah singleton, pembanding yang tepat adalah is, sebagaimana dijelaskan pada Chapter Operator Identity.

auto() dan Iterasi Anggota #

Untuk enumerasi berbasis angka, fungsi auto() mengisi nilai berurutan mulai dari 1, dan seluruh member bisa diiterasi langsung dari class-nya:

python
from enum import Enum, auto

class Level(Enum):
    BRONZE = auto()
    SILVER = auto()
    GOLD = auto()


for level in Level:
    print(f"{level.name} = {level.value}")

print(list(Level))
print(Level(3))

# membership test: hanya anggota terdaftar yang lolos
print("Level.GOLD" in Level.__members__)
print(Level.GOLD in Level)

Output yang Dihasilkan:

text
BRONZE = 1
SILVER = 2
GOLD = 3
[<Level.BRONZE: 1>, <Level.SILVER: 2>, <Level.GOLD: 3>]
Level.GOLD
False
True

Iterasi for level in Level menghasilkan member sesuai urutan deklarasi, dan Level(3) mencari member berdasarkan nilai. Ekspresi Level.GOLD in Level menguji keanggotaan dengan objek member — hasil True — sedangkan string "Level.GOLD" ditolak karena bukan member. Dengan konstanta angka lepas, pengujian seperti if level == 3 tidak memiliki perlindungan semacam ini.

IntEnum dan StrEnum #

Enum biasa tidak bisa dibandingkan langsung dengan nilai mentahnya: StatusPesanan.DIBAYAR == "dibayar" bernilai False. Ketika enumerasi memang harus berbaur dengan integer atau string, enum menyediakan varian campuran:

python
from enum import IntEnum, StrEnum

class Prioritas(IntEnum):
    RENDAH = 1
    TINGGI = 3


class Warna(StrEnum):
    MERAH = "merah"
    BIRU = "biru"


# IntEnum dibandingkan langsung dengan integer
print(Prioritas.TINGGI > Prioritas.RENDAH)
print(Prioritas.TINGGI > 2)
print(Prioritas.TINGGI == 3)

# StrEnum berperilaku seperti string
print(Warna.MERAH.upper())
print(f"Warna favorit: {Warna.BIRU}")
print(Warna.BIRU == "biru")

Output yang Dihasilkan:

text
True
True
True
MERAH
Warna favorit: biru
True

Member IntEnum mengikuti aturan perbandingan integer, dan member StrEnum (hadir sejak Python 3.11) adalah string sungguhan — Warna.BIRU == "biru" bernilai True dan f-string menampilkan "biru", bukan <Warna.BIRU: 'biru'>. Varian ini berguna pada batas sistem lama, misalnya API yang menerima angka atau teks mentah.

Alias, @unique, dan Alasan Meninggalkan Konstanta Biasa #

Nilai duplikat pada Enum tidak menambah member baru, melainkan membuat alias untuk member pertama:

python
from enum import Enum, unique

# nilai duplikat otomatis menjadi alias anggota pertama
class Bentuk(Enum):
    KOTAK = 1
    PERSEGI = 1  # alias untuk KOTAK


print(Bentuk.PERSEGI)
print(Bentuk.KOTAK is Bentuk.PERSEGI)
print(list(Bentuk))

# @unique mencegah alias yang tidak disengaja
try:
    @unique
    class Saluran(Enum):
        EMAIL = 1
        WHATSAPP = 1
except ValueError as e:
    print("ValueError:", e)

Output yang Dihasilkan:

text
Bentuk.KOTAK
True
[<Bentuk.KOTAK: 1>]
ValueError: duplicate values found in <enum 'Saluran'>: WHATSAPP -> EMAIL

Bentuk.PERSEGI hanyalah nama lain untuk Bentuk.KOTAK, dan iterasi hanya menghasilkan member asli. Jika duplikasi tidak disengaja — misalnya salah ketuk nilai — decorator @unique mengubahnya menjadi ValueError yang terlihat segera.