โ† Semua pembelajaran / Python untuk AI Engineer
Fase 1 ยท Sintaks & Idiom Python

dataclasses

Dekorator @dataclass membuat class pembawa data lengkap dengan __init__, __repr__, dan __eq__ dari deklarasi field saja.

Sumber asli docs.python.org Resmi Rangkuman ~6 menit baca

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. Pakai field(default_factory=list).
  • frozen=True membuatnya 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
OpsiEfekKapan dipakai
frozen=TrueTidak bisa diubah; bisa di-hashNilai/konfigurasi yang seharusnya tetap
slots=TrueHemat memoriObjek yang dibuat ribuan kali
kw_only=TrueWajib argumen bernamaClass dengan banyak field
order=TrueBisa 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

@dataclassPydantic BaseModelNamedTuple
Validasi saat dibuatโŒ Tidak sama sekaliโœ… YaโŒ
Parse dari JSONManualโœ… BawaanManual
Hasilkan JSON SchemaโŒโœ… โ€” dipakai untuk tool LLMโŒ
Bisa diubahYa (kecuali frozen)YaTidak
Butuh dependencyTidakYaTidak
Pakai untukData internalData dari luarTuple 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.