Kelas pembawa data biasanya berisi atribut beserta nilai defaultnya, dan tiga metode standarnya berulang di setiap kelas, yaitu __init__, __repr__, dan __eq__. Decorator @dataclass dari modul dataclasses menuliskan ketiganya secara otomatis berdasarkan definisi atributnya, sehingga kelas pembawa data menjadi ringkas tanpa kehilangan perilaku standar. Topik ini membahas penggunaan dasar, nilai default dengan field(), opsi frozen, serta pengisian nilai turunan melalui __post_init__.
Kelas data dasar#
Setiap atribut pada dataclass ditulis sebagai anotasi variabel, yaitu nama diikuti tipe yang diharapkan, tanpa perlu menulis __init__. Dari anotasi tersebut, decorator menghasilkan __init__ dengan parameter sesuai urutan atribut, __repr__ yang menampilkan setiap nilai, dan __eq__ yang membandingkan seluruh atributnya.
from dataclasses import dataclass
@dataclass
class Titik:
x: int
y: int
a = Titik(2, 3)
b = Titik(2, 3)
print(a)
print(a == b)Titik(x=2, y=3)
TrueCetakan Titik(x=2, y=3) berasal dari __repr__ yang dihasilkan otomatis, sedangkan True berasal dari __eq__ yang membandingkan nilai atributnya. Untuk mendapatkan ketiga perilaku itu secara manual, tiga metode harus ditulis sendiri, dan di situlah penghematan dataclass terlihat. Anotasi tipenya tidak diverifikasi oleh interpreter, tetapi dataclass memerlukannya untuk menentukan mana yang menjadi atribut, dan tipe tersebut tetap berguna bagi alat pemeriksa tipe.
Secara bawaan, dataclass dapat berubah, dan perilaku yang dihasilkan dapat disesuaikan lewat argumen decorator, misalnya eq=True atau repr=True yang memang nilai bawaannya. Opsi slots=True yang tersedia sejak Python 3.10 menghasilkan kelas dengan __slots__ untuk menghemat memori pada instans dalam jumlah besar. Kombinasi opsi ini membuat dataclass tetap ringkas tanpa mengunci desainnya.
Nilai default dan field()#
Atribut dengan nilai default cukup diberi tanda sama dengan, dan parameter pada __init__ yang dihasilkan menjadi opsional. Untuk tipe yang dapat diubah seperti list, nilai default tidak boleh dituliskan langsung, melainkan melalui field(default_factory=list), yang memanggil list untuk setiap instans baru. Pola ini menghindarkan state bersama, yaitu masalah yang muncul ketika atribut kelas berupa list dimutasi oleh satu instans.
from dataclasses import dataclass, field
@dataclass
class Barang:
nama: str
jumlah: int = 0
riwayat: list = field(default_factory=list)
def catat(self, kejadian):
self.riwayat.append(kejadian)
b1 = Barang("buku tulis", 12)
b2 = Barang("pensil")
b1.catat("stok masuk 12")
print(b1)
print(b2)Barang(nama='buku tulis', jumlah=12, riwayat=['stok masuk 12'])
Barang(nama='pensil', jumlah=0, riwayat=[])Simpan berkas di atas di direktori kerja, lalu jalankan dengan perintah berikut.
# menjalankan program inventaris
python3 inventaris.pyBarang(nama='buku tulis', jumlah=12, riwayat=['stok masuk 12'])
Barang(nama='pensil', jumlah=0, riwayat=[])Instans b2 dibuat dengan satu argumen saja, karena jumlah dan riwayat memiliki default. Pemanggilan catat() pada b1 tidak memengaruhi b2, karena default_factory membuat list baru untuk setiap instans. Metode catat() juga dapat didefinisikan di badan dataclass, sehingga kelas pembawa data tetap dapat memiliki perilaku kecil miliknya sendiri.
Fungsi field() menampung kebutuhan lain di luar default, misalnya init=False untuk atribut yang tidak menjadi parameter konstruktor dan repr=False untuk atribut yang disembunyikan dari cetakan. Keduanya dibutuhkan pada contoh __post_init__ di bagian berikutnya. Urutan atribut tanpa default harus mendahului yang berdefault, sebagaimana aturan parameter pada fungsi biasa.
frozen=True: dataclass yang tidak dapat diubah#
Opsi frozen=True menghasilkan kelas yang atributnya tidak dapat ditugaskan setelah instans dibuat. Upaya penugasan ditolak dengan FrozenInstanceError, sehingga instans berperilaku seperti tuple, yaitu nilai yang aman dibagikan ke bagian lain program tanpa risiko berubah. Dataclass semacam ini juga dapat menjadi kunci dictionary bila atribut-atributnya dapat di-hash.
from dataclasses import dataclass
@dataclass(frozen=True)
class Koordinat:
lintang: float
bujur: float
k = Koordinat(-6.2, 106.8)
print(k.lintang, k.bujur)
try:
k.lintang = 0.0
except Exception as e:
print(type(e).__name__, str(e))-6.2 106.8
FrozenInstanceError cannot assign to field 'lintang'Pembacaan atribut tetap normal, sedangkan penugasan k.lintang = 0.0 memicu FrozenInstanceError yang menyebut nama atributnya. Opsi ini tepat untuk data yang berfungsi seperti catatan tetap, misalnya koordinat, konfigurasi, atau hasil perhitungan yang tidak boleh berubah di tengah proses. Dataclass frozen juga otomatis mendapatkan __hash__, sehingga instansnya dapat dipakai sebagai kunci dictionary atau anggota set. Apabila perubahan memang diperlukan, fungsi dataclasses.replace() menghasilkan instans baru dengan sebagian nilai yang diganti.
Nilai turunan dengan post_init#
Beberapa atribut tidak diterima dari pemanggil, melainkan dihitung dari atribut lain setelah __init__ selesai. Dataclass menyediakan __post_init__ yang dijalankan otomatis tepat setelah __init__ yang dihasilkannya. Atribut semacam itu dideklarasikan dengan field(init=False) agar tidak ikut menjadi parameter konstruktor.
from dataclasses import dataclass, field
@dataclass
class PersegiPanjang:
panjang: float
lebar: float
luas: float = field(init=False)
def __post_init__(self):
self.luas = self.panjang * self.lebar
p = PersegiPanjang(4, 5)
print(p)PersegiPanjang(panjang=4, lebar=5, luas=20)Konstruktor yang dihasilkan hanya menerima panjang dan lebar, sedangkan luas dihitung oleh __post_init__ dan ikut tampil pada __repr__. Pola ini berguna untuk nilai turunan yang dibaca sering, sebab nilai dihitung sekali saat instans dibuat, alih-alih dihitung ulang setiap kali dibaca. Metode ini juga tempat yang tepat untuk validasi antar atribut, misalnya menolak lebar bernilai negatif dengan raise ValueError. Pada dataclass frozen, penugasan di dalam __post_init__ harus melalui object.__setattr__(self, "luas", ...), karena mekanisme penolakan penugasan baru berlaku setelah inisialisasi selesai. Apabila nilai turunannya tidak ingin ditampilkan, opsi repr=False pada field() menyembunyikannya dari cetakan.
Kesalahan umum#
Kesalahan pertama adalah memberi nilai default list atau dictionary secara langsung, yang ditolak saat kelas didefinisikan.
from dataclasses import dataclass
try:
@dataclass
class Rusak:
item: list = []
except ValueError as e:
print(type(e).__name__, str(e))ValueError mutable default <class 'list'> for field item is not allowed: use default_factoryPesan errornya sudah memuat perbaikannya, yaitu memakai default_factory. Bentuk field(default_factory=list) membuat list baru untuk setiap instans, sehingga masalah state bersama tidak pernah terjadi.
Kesalahan kedua adalah menugaskan atribut pada dataclass frozen, yang selalu berakhir dengan FrozenInstanceError sebagaimana contoh Koordinat. Perbaikannya adalah membuat instans baru, baik lewat pemanggilan konstruktor maupun fungsi dataclasses.replace(). Kesalahan ketiga adalah menulis atribut tanpa anotasi, misalnya jumlah = 0 tanpa : int, karena penugasan biasa di badan kelas dianggap variabel kelas dan tidak menjadi field, sehingga tidak muncul pada __init__ maupun __repr__. Kesalahan ini tidak menimbulkan error saat kelas didefinisikan, dan baru terlihat ketika konstruktor yang diharapkan tidak menerima parameternya. Menambahkan anotasi tipe pada setiap atribut menyelesaikannya sekaligus menjaga dokumentasi kelasnya tetap akurat.
Ringkasan#
- Decorator
@dataclassmenghasilkan__init__,__repr__, dan__eq__dari anotasi atribut pada badan kelas. - Nilai default ditulis langsung untuk tipe tetap, sedangkan tipe yang dapat diubah memakai
field(default_factory=...). - Opsi
frozen=Truemenolak penugasan atribut denganFrozenInstanceError, sehingga instansnya aman dibagikan. - Metode
__post_init__mengisi atribut turunan setelah inisialisasi, denganfield(init=False)agar tidak menjadi parameter konstruktor.
Referensi#
- dataclasses — Data Classes — Python Software Foundation — Dokumentasi modul resmi, mencakup field(), frozen, dan post_init.
- PEP 557 — Data Classes — Python Software Foundation — Rancangan asli fitur dataclass beserta alasan desainnya.