Pendahuluan — object yang “bicara” saat di-print

Di Attribute & __init__ (#42) kita isi state. Di Composition (#47) kita susun siapa punya siapa. Hari ini: bagaimana object menampilkan diri dan dibandingkan — lewat special methods (kadang disebut dunder method: nama ber-underscore ganda) seperti __str__, __repr__, __eq__, plus shortcut modern @dataclass.

Tanpa special method, print(buku) sering hanya alamat memori yang sulit dibaca. Dengan special method, object ikut “sopan” di log, debugger, dan koleksi.

Prasyarat: Nyaman dengan Attribute (#42) dan sudah baca Composition (#47). Fondasi: Encapsulation (#43) · Class & Object (#41) · Mengenal OOP (#40).

Default yang kurang ramah

Class sederhana tanpa special method:

class Buku:
    def __init__(self, judul, penulis):
        self.judul = judul
        self.penulis = penulis


b = Buku("ESP32 Praktis", "Budi")
print(b)
print([b])

Output kurang lebih (alamat hex berbeda tiap jalankan):

<__main__.Buku object at 0x...>
[<__main__.Buku object at 0x...>]

Itu bukan bug — Python belum tahu cara menampilkan objectmu. Kita ajarkan lewat special method.

__str__ — untuk manusia

__str__ dipanggil oleh str(obj) dan print(obj). Isinya sebaiknya singkat dan mudah dibaca orang.

class Buku:
    def __init__(self, judul, penulis):
        self.judul = judul
        self.penulis = penulis

    def __str__(self):
        return f"{self.judul} oleh {self.penulis}"


b = Buku("ESP32 Praktis", "Budi")
print(b)
print(str(b))

Output:

ESP32 Praktis oleh Budi
ESP32 Praktis oleh Budi

__repr__ — untuk developer

__repr__ dipakai saat object muncul di list/dict, REPL, dan log debug. Idealnya jelas dan (bila masuk akal) mirip cara membuat ulang object.

class Buku:
    def __init__(self, judul, penulis):
        self.judul = judul
        self.penulis = penulis

    def __str__(self):
        return f"{self.judul} oleh {self.penulis}"

    def __repr__(self):
        return f"Buku(judul={self.judul!r}, penulis={self.penulis!r})"


b = Buku("ESP32 Praktis", "Budi")
print(b)        # __str__
print([b])      # __repr__ di dalam list
print(repr(b))  # __repr__ eksplisit

Output:

ESP32 Praktis oleh Budi
[Buku(judul='ESP32 Praktis', penulis='Budi')]
Buku(judul='ESP32 Praktis', penulis='Budi')

Kalau hanya __repr__ yang ada, print juga memakainya sebagai fallback. Kalau hanya __str__, list masih memakai default alamat memori — jadi sering tulis keduanya.

print(buku) [buku] / debug __str__ __repr__
Aturan praktis: manusia baca __str__; developer/debug baca __repr__.

__eq__ — kapan dua object “sama”?

Tanpa __eq__, dua instance dengan data sama tetap != karena dibandingkan identitas (alamat), bukan isi:

class Buku:
    def __init__(self, judul, penulis):
        self.judul = judul
        self.penulis = penulis


a = Buku("ESP32 Praktis", "Budi")
b = Buku("ESP32 Praktis", "Budi")
print("tanpa __eq__, a == b?", a == b)
print("a is b?", a is b)

Output:

tanpa __eq__, a == b? False
a is b? False

Sekarang tambahkan __eq__ supaya perbandingan memakai nilai field:

class Buku:
    def __init__(self, judul, penulis):
        self.judul = judul
        self.penulis = penulis

    def __eq__(self, other):
        if not isinstance(other, Buku):
            return NotImplemented
        return self.judul == other.judul and self.penulis == other.penulis


a = Buku("ESP32 Praktis", "Budi")
b = Buku("ESP32 Praktis", "Budi")
c = Buku("Belajar Python", "Sari")
print("a == b?", a == b)
print("a == c?", a == c)
print("a is b?", a is b)

Output:

a == b? True
a == c? False
a is b? False

== soal kesetaraan nilai; is soal object yang sama di memori. Untuk koleksi di Composition (#47), __eq__ membantu cek “buku sudah ada di katalog?” tanpa bergantung pada identitas instance.

@dataclass — class data tanpa boilerplate

Banyak class hanya menyimpan field + ingin __init__, __repr__, dan __eq__ otomatis. Modul dataclasses (stdlib) mengurangi boilerplate itu.

from dataclasses import dataclass


@dataclass
class Buku:
    judul: str
    penulis: str
    tahun: int = 2024


b1 = Buku("ESP32 Praktis", "Budi", 2023)
b2 = Buku("ESP32 Praktis", "Budi", 2023)
print(b1)
print("b1 == b2?", b1 == b2)
print("repr:", repr(b1))

Output kurang lebih:

Buku(judul='ESP32 Praktis', penulis='Budi', tahun=2023)
b1 == b2? True
repr: Buku(judul='ESP32 Praktis', penulis='Budi', tahun=2023)

Catatan: default @dataclass memakai __repr__ bergaya field; kalau mau teks ramah manusia terpisah, tetap tulis __str__ sendiri.

from dataclasses import dataclass


@dataclass
class Buku:
    judul: str
    penulis: str

    def __str__(self):
        return f"{self.judul} oleh {self.penulis}"


print(Buku("Belajar Python", "Sari"))
print([Buku("Belajar Python", "Sari")])

Output:

Belajar Python oleh Sari
[Buku(judul='Belajar Python', penulis='Sari')]

Class biasa vs dataclass — kapan pilih apa?

  • Dataclass — banyak field, sedikit perilaku khusus; ingin __init__/__repr__/__eq__ otomatis
  • Class biasa — validasi kompleks di __init__, banyak method bisnis, atau warisan/ABC yang sudah ramai (lihat ABC (#46))
  • Campur — dataclass untuk model data; class biasa untuk layanan yang punya koleksi model (Composition (#47))

Dataclass bukan “pengganti OOP”. Ia mengurangi boilerplate di class yang memang berpusat pada data.

Pola Dasar — special methods & dataclass

  1. 1
    Tulis __str__ dulu Kalau object sering di-print ke log/UI, buat teks singkat untuk manusia.
  2. 2
    Tambah __repr__ untuk debug Tampilkan nama class + field penting; !r membantu string ber-quote.
  3. 3
    Definisikan __eq__ bila nilai penting Bandingkan field yang masuk akal; kembalikan NotImplemented untuk tipe asing.
  4. 4
    Pertimbangkan @dataclass Field banyak + perilaku tipis? Biarkan stdlib generate boilerplate.
  5. 5
    Jangan paksa dataclass ke mana-mana Layanan kompleks, validasi ketat, atau API tebal — class biasa (atau property) sering lebih jujur.
Ringkas: __str__ manusia · __repr__ debug · __eq__ nilai · dataclass untuk data-heavy.

Kode lengkap — salin & jalankan

Simpan sebagai buku_special_methods.py, lalu python buku_special_methods.py:

from dataclasses import dataclass


@dataclass
class Buku:
    judul: str
    penulis: str
    tahun: int

    def __str__(self):
        return f"{self.judul} · {self.penulis} ({self.tahun})"


class Katalog:
    """Composition: punya daftar Buku (dataclass)."""

    def __init__(self, nama):
        self.nama = nama
        self.items = []

    def tambah(self, buku):
        if buku in self.items:
            return False
        self.items.append(buku)
        return True

    def __str__(self):
        return f"Katalog {self.nama}: {len(self.items)} buku"

    def __repr__(self):
        return f"Katalog(nama={self.nama!r}, items={self.items!r})"


kat = Katalog("Kota A")
b1 = Buku("ESP32 Praktis", "Budi", 2023)
b2 = Buku("ESP32 Praktis", "Budi", 2023)  # nilai sama, instance beda
b3 = Buku("Belajar Python", "Sari", 2024)

print(b1)
print("tambah b1:", kat.tambah(b1))
print("tambah b2 (duplikat nilai):", kat.tambah(b2))
print("tambah b3:", kat.tambah(b3))
print(kat)
print("items:", kat.items)
print("b1 == b2?", b1 == b2)

Output yang diharapkan (kurang lebih):

ESP32 Praktis · Budi (2023)
tambah b1: True
tambah b2 (duplikat nilai): False
tambah b3: True
Katalog Kota A: 2 buku
items: [Buku(judul='ESP32 Praktis', penulis='Budi', tahun=2023), Buku(judul='Belajar Python', penulis='Sari', tahun=2024)]
b1 == b2? True

Di sini dataclass memberi __eq__ otomatis; Katalog class biasa memakai composition + __str__/__repr__ sendiri — pola yang sering muncul di proyek nyata.

Kesalahan umum

Gejala Penyebab tipikal Perbaikan
print masih alamat memori Belum ada __str__/__repr__ Tambah minimal salah satu; idealnya keduanya
List menampilkan jelek meski print bagus Hanya __str__, tanpa __repr__ Tulis __repr__ juga
Dua buku “sama” tapi == False Belum __eq__ (atau bukan dataclass) Implement __eq__ atau pakai @dataclass
Bingung == vs is Menyamakan kesetaraan nilai dengan identitas == nilai; is object yang sama di memori
Dataclass dipakai untuk service tebal Memaksa shortcut ke class yang banyak side-effect Class biasa + composition; dataclass untuk model data
TypeError: unhashable type setelah tulis __eq__ Custom __eq__ membuat object tidak hashable (default) Untuk Seri 3: bandingkan dengan == / in list. Jangan masukkan ke set/dict key sebelum paham __hash__

Latihan singkat

  1. Tambah __str__ pada class Ebook (lihat pola di Inheritance (#44)) yang menampilkan format file.
  2. Ubah Katalog.tambah agar menolak duplikat berdasarkan judul saja (bukan seluruh field).
  3. Bandingkan: class Buku manual dengan __init__/__repr__/__eq__ vs versi @dataclass — mana yang lebih pendek?

FAQ singkat

Apakah harus selalu tulis __str__ dan __repr__?
Tidak wajib, tapi sangat membantu begitu object sering di-print atau debug. Dataclass sudah memberi __repr__ default.

Apa bedanya dengan Encapsulation (#43)?
Encapsulation mengatur akses/validasi field. Special methods mengatur bagaimana object ditampilkan dan dibandingkan.

Apakah dataclass bisa inheritance?
Bisa, dengan hati-hati (urutan field default). Untuk belajar Seri 3, mulai dari dataclass “datar” dulu.

Kenapa kembalikan NotImplemented di __eq__?
Supaya Python bisa mencoba perbandingan dari sisi object lain, bukan langsung False yang menyesatkan.

Kenapa muncul unhashable type setelah __eq__?
Python menonaktifkan hash default bila kamu tulis __eq__. Untuk katalog di list, buku in items memakai == — aman. set/dict key butuh __hash__ konsisten (topik lanjutan).

Apakah ini menggantikan Composition (#47)?
Tidak. Special methods membuat tiap object lebih sopan; composition tetap menjawab “siapa punya siapa”.

Kesimpulan & langkah berikutnya

__str__ untuk manusia, __repr__ untuk debug, __eq__ untuk kesetaraan nilai. @dataclass memotong boilerplate class data-heavy — tetap kombinasikan dengan composition untuk layanan yang mengelola koleksi.

Artikel ini adalah #48 (ini) — langkah kesembilan Seri 3 setelah Composition vs Inheritance (#47).

Lanjut ke artikel berikutnya — Capstone: Sistem Perpustakaan Mini: gabungkan class, encapsulation, inheritance, polymorphism, ABC, composition, dan special methods dalam satu CLI kecil. (Belum di-hyperlink sampai artikel itu live.)

Seri 3 progress: 9/10 artikel live. Kamu di langkah #48 (ini). Prasyarat: Composition (#47) · Attribute (#42) · Encapsulation (#43) · Abstraction (#46) · Polymorphism (#45) · Inheritance (#44) · Class & Object (#41) · Mengenal OOP (#40).