Dunder method

Dunder method seperti __str__, __repr__, __eq__, dan __add__ untuk tipe yang Pythonic.

Daftar isi

Dunder method adalah metode khusus yang namanya diawali dan diakhiri dua garis bawah, seperti __str__ dan __add__. Python memanggil metode-metode ini secara otomatis ketika objek memenuhi sintaks tertentu, misalnya dicetak, dibandingkan, atau dijumlahkan. Dengan mengisinya, kelas buatan sendiri dapat berperilaku seperti tipe bawaan, yaitu tercetak rapi, dapat dibandingkan, dan mendukung operator. Topik ini membahas __repr__, __str__, __eq__, serta __add__ beserta protokolnya.

repr dan str#

Dua metode pertama yang layak diisi adalah __repr__ dan __str__. Metode __repr__ menghasilkan representasi teknis yang bertujuan tidak ambigu, biasanya bentuk yang dapat dibaca ulang oleh interpreter, sedangkan __str__ menghasilkan teks yang nyaman dibaca manusia. Fungsi print() dan f-string memakai __str__, sedangkan kontainer seperti list memakai __repr__ untuk setiap isinya. Tanpa keduanya, cetakan hanya memuat nama kelas beserta alamat memori objeknya.

python Contoh: __repr__ dan __str__ pada kelas Buku
class Buku:
    def __init__(self, judul, halaman):
        self.judul = judul
        self.halaman = halaman

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

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

b = Buku("Kamus Mini", 320)
print(b)
print(repr(b))
print([b, b])
text Keluaran
Kamus Mini (320 halaman)
Buku('Kamus Mini', 320)
[Buku('Kamus Mini', 320), Buku('Kamus Mini', 320)]

print(b) memanggil __str__ sehingga menghasilkan teks yang nyaman dibaca. Fungsi repr(b) memanggil __repr__ dan menghasilkan bentuk Buku('Kamus Mini', 320), dengan penanda !r di dalam f-string yang memberi tanda kutip pada string judul. Saat objek berada di dalam list, Python memakai __repr__ untuk tiap isinya, sehingga cetakan list tetap informatif. Apabila __str__ tidak ditulis, Python memakai __repr__ sebagai cadangan, sehingga mendefinisikan __repr__ saja sudah banyak membantu.

Pencarian dunder method terjadi pada kelasnya, bukan pada instansnya. Menugaskan __repr__ langsung pada sebuah objek tidak membuat cetakannya berubah, karena operator dan fungsi bawaan selalu menelusuri kelas objek tersebut. Aturan inilah yang membuat dunder method berbeda dari metode biasa yang bebas dipasang pada instans.

eq untuk kesetaraan nilai#

Secara bawaan, dua instans berbeda dianggap tidak sama meskipun atributnya identik, karena perbandingan memakai identitas objek. Metode __eq__ mengubah aturan itu menjadi kesetaraan nilai, sehingga objek1 == objek2 membandingkan isi yang relevan. Metode ini diharapkan mengembalikan NotImplemented bila jenis pembandingnya tidak dikenal, agar Python mencoba arah pembandingan sebaliknya.

python Contoh: kesetaraan nilai dengan __eq__
class Titik:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Titik({self.x}, {self.y})"

    def __eq__(self, lain):
        if not isinstance(lain, Titik):
            return NotImplemented
        return (self.x, self.y) == (lain.x, lain.y)

a = Titik(2, 3)
b = Titik(2, 3)
c = Titik(4, 1)
print(a == b)
print(a == c)
print(a != c)
print(a == (2, 3))
text Keluaran
True
False
True
False

Kedua instans dengan nilai sama menghasilkan True, dan baris a != c menegaskan bahwa operator != otomatis mengikuti kebalikan __eq__ tanpa perlu ditulis. Pembandingan a == (2, 3) menghasilkan False karena __eq__ mengembalikan NotImplemented untuk tuple, dan Python kembali ke perbandingan identitas. Operator in pada list serta pengujian otomatis juga mengandalkan __eq__, sehingga mendefinisikannya bermanfaat bagi banyak mekanisme sekaligus.

add dan protokol operator#

Metode __add__ menghubungkan operator + pada objek. Metode ini menerima operand kanan, menghitung hasilnya, lalu mengembalikan objek baru. Keluarga metode yang sama menangani operator lain, misalnya __sub__ untuk - dan __mul__ untuk *, sedangkan __len__ menghubungkan fungsi len(). Seluruh keluarga ini disebut protokol operator, dan kelengkapannya dirinci pada referensi bahasa.

python Contoh: penjumlahan vektor dengan __add__
class Vektor2:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Vektor2({self.x}, {self.y})"

    def __add__(self, lain):
        return Vektor2(self.x + lain.x, self.y + lain.y)

    def __len__(self):
        return 2

u = Vektor2(3, 1)
v = Vektor2(1, 4)
print(u + v)
print(len(u))
text Keluaran
Vektor2(4, 5)
2

Ekspresi u + v diterjemahkan menjadi u.__add__(v), dan metode itu mengembalikan objek Vektor2 baru alih-alih mengubah u maupun v. Mengembalikan objek baru adalah konvensi operator aritmetika pada tipe bawaan Python, sehingga ekspresi dapat dirangkai tanpa risiko operandnya berubah. Apabila operand kanan mendukung operasi tersebut tetapi operand kiri tidak, Python mencoba metode pantulan seperti __radd__, dan pasangan ini lazim didefinisikan bersama pada tipe angka buatan. Fungsi len(u) diteruskan ke __len__ yang mengembalikan 2 sesuai jumlah komponen vektornya.

Program utuh: kelas Jam#

Berkas berikut memadukan __add__, __eq__, dan __repr__ pada satu kelas Jam yang menangani penambahan menit dengan pengulangan 24 jam.

python jam.py
class Jam:
    def __init__(self, jam, menit):
        total = jam * 60 + menit
        self.jam = (total // 60) % 24
        self.menit = total % 60

    def __repr__(self):
        return f"Jam({self.jam:02d}:{self.menit:02d})"

    def __add__(self, menit):
        return Jam(self.jam, self.menit + menit)

    def __eq__(self, lain):
        return (self.jam, self.menit) == (lain.jam, lain.menit)

j = Jam(23, 45) + 30
print(j)
print(j == Jam(0, 15))
text Keluaran
Jam(00:15)
True

Simpan berkas di atas di direktori kerja, lalu jalankan dengan perintah berikut.

bash
# menjalankan program kelas Jam
python3 jam.py
text Keluaran
Jam(00:15)
True

__init__ mengubah seluruh waktu menjadi total menit lalu membacanya kembali dengan % 24, sehingga kelebihan 24 jam kembali ke awal hari secara otomatis. Normalisasi hanya ada satu tempat, yaitu konstruktor, sehingga metode lain tidak perlu mengulang perhitungannya. Penjumlahan 30 menit pada pukul 23.45 menghasilkan Jam(00:15), dan __repr__ dengan penentu format :02d menjaga tampilan dua digit. Karena __add__ mengembalikan objek baru, penjumlahan dapat dirangkai, misalnya j + 90 + 30, tanpa mengubah objek perantara. Kesetaraan membandingkan pasangan jam dan menit, sehingga j == Jam(0, 15) bernilai True meskipun keduanya dibuat dengan cara yang berbeda.

Kesalahan umum#

Kesalahan pertama adalah lupa mengembalikan objek baru pada protokol operator, sehingga hasil operasi bernilai None.

python Contoh: __add__ tanpa return
class JumlahRusak:
    def __init__(self, nilai):
        self.nilai = nilai

    def __add__(self, lain):
        self.nilai += lain.nilai

a = JumlahRusak(3)
b = JumlahRusak(4)
hasil = a + b
print(hasil)
text Keluaran
None

Metode yang tidak memuat return mengembalikan None, sehingga ekspresi a + b tidak menghasilkan apa pun untuk disimpan. Selain itu, versi ini mengubah operand kirinya tanpa pesan error. Perbaikannya adalah menghitung nilai baru lalu mengembalikan objek kelasnya, sebagaimana contoh Vektor2 dan Jam.

Kesalahan kedua adalah mencetak objek yang kelasnya belum memiliki __repr__. Cetakan berisi bentuk seperti <__main__.Jam object at 0x...> yang memuat alamat memori dan berbeda pada setiap eksekusi, sehingga tidak berguna untuk pembacaan maupun pengujian. Mendefinisikan __repr__ adalah perbaikan satu langkah, dan kebiasaan ini juga menjaga keluaran program tetap dapat direproduksi. Kesalahan ketiga adalah menimbulkan exception pada __eq__ ketika pembandingnya bukan jenis yang dikenal, padahal __eq__ seharusnya mengembalikan NotImplemented agar Python dapat mencoba arah sebaliknya atau kembali ke perilaku bawaan.

Ringkasan#

  • Dunder method dipanggil Python secara otomatis dari sintaks, sehingga print() memakai __str__, == memakai __eq__, dan + memakai __add__.
  • __repr__ menyajikan representasi teknis yang tidak ambigu, sedangkan __str__ menyajikan teks yang nyaman dibaca.
  • Operator aritmetika sebaiknya mengembalikan objek baru dan menggunakan NotImplemented bila operandnya tidak dikenal.
  • Mendefinisikan __eq__ tanpa __hash__ membuat instans tidak dapat dipakai di set atau sebagai kunci dictionary.

Referensi#