Menulis kelas Python yang hanya berfungsi menyimpan data sering kali berakhir dengan boilerplate yang berulang-ulang: metode __init__ yang panjang, __repr__ agar objek mudah di-debug, dan __eq__ supaya dua objek bisa dibandingkan. Semua itu bisa ditulis manual, tapi melelahkan dan rawan salah. Untungnya, Python punya solusi bawaan sejak versi 3.7: dataclasses. Dengan satu decorator @dataclass — yang pada dasarnya hanyalah sebuah decorator Python biasa — kamu bisa membuat kelas data yang rapi, mudah dibaca, dan otomatis dilengkapi metode-metode penting. Di tutorial ini kita akan membahas dataclass secara menyeluruh, dari dasar hingga fitur canggih seperti field(), frozen, slots, dan __post_init__, lengkap dengan contoh kode yang bisa langsung kamu jalankan.
Apa Itu Dataclass?
Dataclass adalah cara untuk mendeklarasikan kelas yang sebagian besar isinya adalah data. Kamu cukup menuliskan atribut beserta type hint-nya, lalu Python akan men-generate metode-metode umum secara otomatis: __init__, __repr__, __eq__, dan (jika diminta) __lt__, __le__, __gt__, __ge__, serta __hash__. Dataclass tersedia di modul dataclasses dalam pustaka standar, jadi kamu tidak perlu menginstal apa pun.
Sebelum & Sesudah: Hapus Boilerplate
Mari kita lihat perbedaannya secara langsung. Berikut ini kelas data sederhana yang ditulis manual seperti cara tradisional:
class ProductManual:
def __init__(self, name: str, price: float, quantity: int = 0):
self.name = name
self.price = price
self.quantity = quantity
def __repr__(self):
return (
f"ProductManual(name={self.name!r}, "
f"price={self.price!r}, quantity={self.quantity!r})"
)
def __eq__(self, other):
if not isinstance(other, ProductManual):
return NotImplemented
return (
self.name, self.price, self.quantity
) == (other.name, other.price, other.quantity)
p1 = ProductManual("Laptop", 15_000_000, 3)
p2 = ProductManual("Laptop", 15_000_000, 3)
print(p1) # repr manual
print(p1 == p2) # True
Bandingkan dengan versi dataclass yang setara:
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
quantity: int = 0
p1 = Product("Laptop", 15_000_000, 3)
p2 = Product("Laptop", 15_000_000, 3)
print(p1) # Product(name='Laptop', price=15000000, quantity=3)
print(p1 == p2) # True
Perhatikan bahwa dataclass membaca urutan field untuk membuat __init__. Field tanpa nilai default (seperti name dan price) harus ditulis sebelum field yang punya default (seperti quantity), persis seperti aturan parameter fungsi biasa.
Parameter @dataclass: init, repr, eq, order, dan frozen
Decorator @dataclass menerima beberapa argumen untuk mengontrol metode apa saja yang dibuat. Nilai default-nya adalah init=True, repr=True, eq=True, order=False, dan frozen=False.
from dataclasses import dataclass
@dataclass(order=True)
class Book:
title: str
pages: int
b1 = Book("Python Dasar", 300)
b2 = Book("Python Lanjutan", 500)
print(b1 < b2) # True -> dibandingkan berdasarkan urutan field (title dulu, lalu pages)
Ketika order=True, Python otomatis membuat metode perbandingan (<, <=, >, >=). Catatan penting: order=True hanya bisa dipakai jika eq=True.
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
host: str
port: int = 5432
cfg = Config("localhost")
print(cfg.port) # 5432
# cfg.port = 3306 # Akan melempar FrozenInstanceError
Dengan frozen=True, objek menjadi immutable (tidak bisa diubah setelah dibuat) dan secara otomatis dapat dipakai sebagai key di dict atau elemen di set, karena __hash__ ikut dibuat.
Nilai Default dan Fungsi field()
Jangan pernah memakai objek mutable (seperti list, dict, atau set) langsung sebagai nilai default. Default tersebut dibuat sekali saat kelas didefinisikan, sehingga semua instance akan berbagi list yang sama dan saling menimpa data satu sama lain.
from dataclasses import dataclass, field
@dataclass
class ShoppingCart:
items: list = field(default_factory=list) # benar: list baru per instance
total: float = 0.0
cart1 = ShoppingCart()
cart2 = ShoppingCart()
cart1.items.append("Mouse")
print(cart1.items) # ['Mouse']
print(cart2.items) # [] <- tidak saling berbagi list!
Fungsi field() punya beberapa opsi berguna selain default dan default_factory, yaitu init, repr, dan compare. Contohnya, kita bisa menyembunyikan field sensitif dari repr:
from dataclasses import dataclass, field
@dataclass
class User:
username: str
password: str = field(repr=False) # tidak tampil di repr
roles: list = field(default_factory=list, compare=False)
u = User("budi", "rahasia123")
print(u) # User(username='budi') <- password aman
Validasi dan Perhitungan dengan __post_init__
Dataclass menyediakan hook __post_init__ yang dipanggil otomatis setelah __init__ selesai. Ini tempat yang tepat untuk validasi input atau menghitung field turunan yang tidak boleh diisi lewat konstruktor.
from dataclasses import dataclass, field
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False) # tidak jadi parameter __init__
def __post_init__(self):
self.area = self.width * self.height
r = Rectangle(4, 5)
print(r.area) # 20.0
from dataclasses import dataclass
@dataclass
class Temperature:
celsius: float
def __post_init__(self):
if self.celsius < -273.15:
raise ValueError("Suhu tidak mungkin di bawah nol mutlak")
Temperature(25) # OK
# Temperature(-300) # ValueError
Pewarisan (Inheritance) pada Dataclass
Dataclass bisa diwariskan seperti kelas biasa. Field dari kelas induk akan otomatis digabungkan ke dalam __init__ kelas anak.
from dataclasses import dataclass
@dataclass
class Animal:
name: str
age: int = 0
@dataclass
class Dog(Animal):
breed: str = "campuran"
d = Dog("Bolt", 3, "Golden Retriever")
print(d) # Dog(name='Bolt', age=3, breed='Golden Retriever')
Aturan urutan field tetap berlaku saat pewarisan: jika kelas induk punya field dengan nilai default, maka semua field baru di kelas anak juga wajib diberi nilai default, supaya tidak ada parameter non-default yang muncul setelah parameter default.
Fitur Modern: slots=True dan kw_only
Sejak Python 3.10, dataclass mendukung slots=True untuk menghemat memori (terutama saat membuat jutaan instance), dan kw_only=True untuk memaksa semua field diisi lewat keyword argument.
from dataclasses import dataclass
@dataclass(slots=True)
class Point:
x: int
y: int
@dataclass(kw_only=True)
class UserProfile:
name: str
email: str
age: int = 0
u = UserProfile(name="Budi", email="budi@example.com")
print(u)
Konversi ke dict dan tuple: asdict() & astuple()
Modul dataclasses juga menyediakan asdict() dan astuple() untuk mengubah instance menjadi dict atau tuple. Ini sangat berguna saat ingin menyimpan data ke JSON atau menggabungkannya dengan alat analisis data.
from dataclasses import dataclass, asdict, astuple
@dataclass
class Point:
x: int
y: int
p = Point(3, 4)
print(asdict(p)) # {'x': 3, 'y': 4}
print(astuple(p)) # (3, 4)
Kombinasi yang umum adalah mengubah daftar dataclass menjadi DataFrame untuk analisis lebih lanjut. Jika kamu belum familier, kamu bisa mempelajarinya di panduan Pandas untuk Analisis Data:
import pandas as pd
from dataclasses import dataclass, asdict
@dataclass
class Sale:
product: str
qty: int
price: float
sales = [Sale("Mouse", 2, 150_000), Sale("Keyboard", 1, 400_000)]
df = pd.DataFrame([asdict(s) for s in sales])
print(df)
Dataclass vs NamedTuple vs Pydantic
Selain dataclass, Python punya namedtuple dari modul collections untuk hal serupa, tetapi namedtuple bersifat immutable dan lebih terbatas (tidak punya __post_init__ atau field() yang fleksibel). Sementara itu, untuk kebutuhan validasi data yang jauh lebih ketat — misalnya memvalidasi input dari API atau konfigurasi — pustaka seperti Pydantic adalah pilihan yang lebih tepat.
Aturan praktisnya: pakai dataclass untuk struktur data internal aplikasi yang ringan dan cepat, lalu beralih ke Pydantic ketika kamu butuh parsing, validasi, dan serialisasi data dari luar (misalnya request HTTP atau file JSON). Keduanya sering dipakai berdampingan di proyek nyata.
Kapan Menggunakan Dataclass?
Dataclass paling pas untuk: model data sederhana, objek konfigurasi, hasil query database, DTO (Data Transfer Object), dan struktur data internal. Sebaliknya, jangan paksa dataclass untuk kelas yang punya banyak perilaku (metode bisnis kompleks) atau yang membutuhkan validasi eksternal yang ketat.
Dataclass adalah salah satu fitur Python yang paling banyak dipakai di proyek produksi karena menghilangkan boilerplate sekaligus tetap eksplisit dan mudah dibaca. Dengan memahami field(), default_factory, frozen, slots, dan __post_init__, kamu sudah siap memanfaatkannya secara maksimal dalam kode kamu sendiri. Selamat mencoba!