dataclasses
Dekorator @dataclass membuat class pembawa data lengkap dengan __init__, __repr__, dan __eq__ dari deklarasi field saja.
Intisari
- Satu dekorator menggantikan
__init__,__repr__, dan__eq__yang ditulis tangan. - Field dideklarasikan pakai type hint. Anotasinya wajib โ tanpa itu field tidak terdaftar.
- Default mutable (
= []) adalah error. Pakaifield(default_factory=list). frozen=Truemembuatnya immutable dan bisa jadi key dict atau anggota set.- Untuk data dari luar (JSON, API, env) pakai Pydantic โ dataclass tidak memvalidasi apa pun.
Sebelum dan sesudah
# Ditulis tangan โ 15 baris untuk 3 field
class Dokumen:
def __init__(self, id, isi, skor=0.0):
self.id = id
self.isi = isi
self.skor = skor
def __repr__(self):
return f"Dokumen(id={self.id!r}, isi={self.isi!r}, skor={self.skor!r})"
def __eq__(self, other):
if not isinstance(other, Dokumen):
return NotImplemented
return (self.id, self.isi, self.skor) == (other.id, other.isi, other.skor)
# Dengan dataclass โ semuanya di-generate
from dataclasses import dataclass
@dataclass
class Dokumen:
id: str
isi: str
skor: float = 0.0
d = Dokumen("abc", "isi dokumen")
print(d) # Dokumen(id='abc', isi='isi dokumen', skor=0.0)
d == Dokumen("abc", "isi dokumen") # True โ dibandingkan per-nilai, bukan per-identitas
Jebakan default mutable
@dataclass
class Buruk:
tag: list[str] = [] # โ TypeError saat class didefinisikan
Untungnya dataclass menolak ini secara terang-terangan. Fungsi biasa tidak:
def tambah(item, ke=[]): # โ tidak ada error, tapi ini bug
ke.append(item)
return ke
tambah("a") # ['a']
tambah("b") # ['a', 'b'] โ list yang SAMA dipakai ulang!
Penyebabnya: nilai default dievaluasi sekali, saat fungsi didefinisikan โ
bukan setiap kali dipanggil. Jadi satu list yang sama dibagi ke semua pemanggilan.
Ini salah satu jebakan Python yang paling terkenal, dan Ruff aturan B006 menangkapnya.
Solusinya:
from dataclasses import dataclass, field
@dataclass
class Dokumen:
id: str
tag: list[str] = field(default_factory=list) # list baru per instance
meta: dict[str, str] = field(default_factory=dict)
# Untuk fungsi biasa:
def tambah(item, ke: list | None = None) -> list:
ke = ke if ke is not None else []
ke.append(item)
return ke
Opsi field() yang berguna
from dataclasses import dataclass, field
@dataclass
class Konfigurasi:
nama: str
tag: list[str] = field(default_factory=list)
rahasia: str = field(repr=False, default="") # jangan muncul di print/log
dihitung: float = field(init=False, default=0.0) # tidak jadi parameter __init__
catatan: str = field(compare=False, default="") # diabaikan saat ==
repr=False sangat berguna untuk API key โ supaya tidak bocor ke log secara tidak sengaja.
Opsi dekorator
@dataclass(frozen=True) # immutable โ bisa jadi key dict / anggota set
class Titik:
x: float
y: float
@dataclass(slots=True) # hemat memori, akses atribut lebih cepat
class Chunk:
teks: str
embedding: list[float]
@dataclass(kw_only=True) # semua field wajib pakai nama saat dibuat
class Permintaan:
model: str
max_tokens: int
| Opsi | Efek | Kapan dipakai |
|---|---|---|
frozen=True | Tidak bisa diubah; bisa di-hash | Nilai/konfigurasi yang seharusnya tetap |
slots=True | Hemat memori | Objek yang dibuat ribuan kali |
kw_only=True | Wajib argumen bernama | Class dengan banyak field |
order=True | Bisa dibandingkan < > | Objek yang mau diurutkan |
Fungsi pembantu
from dataclasses import asdict, astuple, replace, fields
asdict(d) # {'id': 'abc', 'isi': '...', 'skor': 0.0} โ rekursif
astuple(d) # ('abc', '...', 0.0)
replace(d, skor=0.9) # salinan baru dengan satu field diubah (penting untuk frozen)
[f.name for f in fields(Dokumen)] # ['id', 'isi', 'skor']
Post-init
@dataclass
class Chunk:
teks: str
jumlah_kata: int = field(init=False)
def __post_init__(self) -> None:
self.jumlah_kata = len(self.teks.split())
dataclass vs Pydantic vs NamedTuple
@dataclass | Pydantic BaseModel | NamedTuple | |
|---|---|---|---|
| Validasi saat dibuat | โ Tidak sama sekali | โ Ya | โ |
| Parse dari JSON | Manual | โ Bawaan | Manual |
| Hasilkan JSON Schema | โ | โ โ dipakai untuk tool LLM | โ |
| Bisa diubah | Ya (kecuali frozen) | Ya | Tidak |
| Butuh dependency | Tidak | Ya | Tidak |
| Pakai untuk | Data internal | Data dari luar | Tuple bernama sederhana |
Aturan praktisnya: data yang kamu buat dan pakai di dalam prosesmu sendiri โ
@dataclass. Data yang datang dari luar โ request API, respons LLM, file JSON,
environment variable โ Pydantic. Perbedaannya adalah soal kepercayaan:
data dari luar harus divalidasi.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.