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.
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])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.
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))True
False
True
FalseKedua 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.
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))Vektor2(4, 5)
2Ekspresi 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.
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))Jam(00:15)
TrueSimpan berkas di atas di direktori kerja, lalu jalankan dengan perintah berikut.
# menjalankan program kelas Jam
python3 jam.pyJam(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.
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)NoneMetode 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
NotImplementedbila operandnya tidak dikenal. - Mendefinisikan
__eq__tanpa__hash__membuat instans tidak dapat dipakai di set atau sebagai kunci dictionary.
Referensi#
- Special method names — Python Software Foundation — Daftar lengkap dunder method dan sintaks yang memicunya.
- Emulating numeric types — Python Software Foundation
— Protokol operator aritmetika seperti
__add__beserta aturan NotImplemented. - object.eq — Python Software Foundation — Semantik perbandingan kesetaraan dan hubungannya dengan hash.