Sebagian besar class pada program nyata adalah class data: Produk, Pelanggan, Konfigurasi — objeknya hidup untuk menyimpan sekumpulan nilai. Jika ditulis manual, class seperti itu menuntut tiga method boilerplate yang polanya selalu sama: __init__ menyalin parameter ke atribut, __repr__ merangkai teks debug, dan __eq__ membandingkan isi. Module dataclasses (tersedia sejak Python 3.7) memindahkan ketiganya ke satu decorator @dataclass, sehingga kita cukup mendeklarasikan field beserta tipenya.
Decorator @dataclass dan Method Otomatis #
Saat interpreter Python mengeksekusi @dataclass, decorator tersebut memindai annotasi tipe pada class body, lalu menghasilkan __init__, __repr__, dan __eq__ secara otomatis berdasarkan daftar field. Sintaks decorator @ ini sudah kita pakai pada Chapter classmethod dan staticmethod.
>>> from dataclasses import dataclass
>>> @dataclass
... class Produk:
... nama: str
... harga: int
...
>>> Produk("Kopi Gayo", 45000)
Produk(nama='Kopi Gayo', harga=45000)
>>> Produk("Kopi Gayo", 45000) == Produk("Kopi Gayo", 45000)
TrueTiga baris deklarasi nama: str dan harga: int menghasilkan class yang lengkap:
__init__menerimanamadanhargasebagai parameter positional dan menyimpannya sebagai atribut instance.__repr__mencetakProduk(nama='Kopi Gayo', harga=45000)— bentuk yang persis seperti ekspresi pembuatan objeknya, sehingga mudah direkonstruksi saat debugging.__eq__membandingkan semua field, bukan identitas objek — dua objek berbeda dengan isi sama dilaporkanTrue.
Perbandingan dengan Class Manual #
Untuk merasakan penghematan kode, mari kita bandingkan class manual dengan hasil @dataclass secara berdampingan. Kedua class di bawah ini berperilaku identik:
class ProdukManual:
def __init__(self, nama, harga, stok):
self.nama = nama
self.harga = harga
self.stok = stok
def __repr__(self):
return (f"ProdukManual(nama={self.nama!r}, "
f"harga={self.harga!r}, stok={self.stok!r})")
def __eq__(self, other):
if not isinstance(other, ProdukManual):
return NotImplemented
return (self.nama, self.harga, self.stok) == (other.nama, other.harga, other.stok)
from dataclasses import dataclass
@dataclass
class Produk:
nama: str
harga: int
stok: int = 0
a = Produk("Kopi Gayo", 45000, 10)
b = Produk("Kopi Gayo", 45000, 10)
print(a)
print(a == b)
print(a == Produk("Kopi Toraja", 38000, 5))
m = ProdukManual("Kopi Gayo", 45000, 10)
n = ProdukManual("Kopi Gayo", 45000, 10)
print(m)
print(m == n)Output yang Dihasilkan:
Produk(nama='Kopi Gayo', harga=45000, stok=10)
True
False
ProdukManual(nama='Kopi Gayo', harga=45000, stok=10)
TrueMari kita telaah bagian dataclass pada kode di atas:
- Baris 20 (
@dataclass): decorator membaca tiga field bernamanama,harga, danstokdari class body, lalu menghasilkan tiga method otomatis tanpa kita menulis satu baris pun. - Baris 24 (
stok: int = 0): field dengan nilai default;Produk("Kopi", 45000)sah karenastokotomatis bernilai0. Field tanpa default wajib ditulis lebih dulu. - Baris 27 (
a = Produk(...)): konstruktor yang dihasilkan menerima ketiga field secara positional maupun keyword —Produk(nama="Kopi Gayo", harga=45000)juga sah;bpada baris berikutnya dibuat dengan isi identik. - Baris 32 (
a == Produk(...)): perbandingan menghasilkanFalsekarena fieldnamadanhargaberbeda — sama persis dengan perilaku__eq__manual pada class di bagian atas.
Empat belas baris class manual runtuh menjadi lima baris deklarasi, dan setiap penambahan field hanya membutuhkan satu baris.
Nilai Default dan field(default_factory=…) #
Field mutable seperti list dan dict tidak boleh dijadikan default secara langsung — Python menolaknya saat decorator dieksekusi. Aturan ini sengaja diberlakukan karena satu objek default akan dibagikan oleh semua instance, persis jebakan mutable default pada Chapter Default Parameters.
from dataclasses import dataclass, field
# --- jebakan: default mutable langsung ---
try:
@dataclass
class KeranjangSalah:
pelanggan: str
items: list = []
except ValueError as e:
print("ValueError:", e)
# --- solusi: default_factory ---
@dataclass
class Keranjang:
pelanggan: str
items: list = field(default_factory=list)
keranjang_budi = Keranjang("Budi")
keranjang_siti = Keranjang("Siti")
keranjang_budi.items.append("kopi")
print(keranjang_budi)
print(keranjang_siti)Output yang Dihasilkan:
ValueError: mutable default <class 'list'> for field items is not allowed: use default_factory
Keranjang(pelanggan='Budi', items=['kopi'])
Keranjang(pelanggan='Siti', items=[])Argumen default_factory=list meminta interpreter memanggil list() yang baru setiap kali objek Keranjang dibuat, sehingga keranjang Budi dan Siti benar-benar terpisah — item milik Budi tidak pernah bocor ke keranjang Siti.
Data Immutable dengan frozen=True #
Konfigurasi dan nilai seperti koordinat sering tidak boleh berubah setelah dibuat. Parameter frozen=True menghasilkan class yang menolak penugasan atribut, selaras dengan materi Chapter Mutable dan Immutable Objects.
from dataclasses import dataclass
@dataclass(frozen=True)
class Konfigurasi:
host: str
port: int
config = Konfigurasi("db.example.com", 5432)
print(config)
try:
config.port = 3306
except Exception as e:
print(type(e).__name__ + ":", e)
# frozen dataclass otomatis hashable -> bisa jadi anggota set
db1 = Konfigurasi("db.example.com", 5432)
db2 = Konfigurasi("db.example.com", 5432)
print(len({db1, db2}))Output yang Dihasilkan:
Konfigurasi(host='db.example.com', port=5432)
FrozenInstanceError: cannot assign to field 'port'
1Penugasan config.port = 3306 memicu FrozenInstanceError — dataclass versi beku memblokir mutasi sejak awal. Sebagai bonus, class beku otomatis hashable, sehingga db1 dan db2 yang setara terhitung sebagai satu anggota set.
slots=True dan order=True #
Dua parameter lain yang sering dipakai:
from dataclasses import dataclass
@dataclass(slots=True)
class Titik:
x: int
y: int
p = Titik(3, 4)
print(p)
try:
p.z = 5
except AttributeError as e:
print("AttributeError:", e)
@dataclass(order=True)
class Versi:
nomor: int
versi_lama = Versi(2)
versi_baru = Versi(3)
print(versi_lama < versi_baru)
print(sorted([Versi(3), Versi(1), Versi(2)]))Output yang Dihasilkan:
Titik(x=3, y=4)
AttributeError: 'Titik' object has no attribute 'z'
True
[Versi(nomor=1), Versi(nomor=2), Versi(nomor=3)]Parameter slots=True (Python 3.10+) menghasilkan class hemat memori yang menolak atribut baru — mekanisme lengkapnya dibahas pada Chapter slots. Parameter order=True menghasilkan __lt__, __le__, __gt__, dan __ge__ yang membandingkan field secara berurutan, sehingga sorted() langsung bekerja.