Mengapa len(daftar) bekerja pada list, dan for bisa mengiterasi hampir semua objek? Jawabannya ada pada data model Python: setiap sintaks bahasa — pemanggilan, len(), in, for, print() — pada akhirnya diterjemahkan menjadi pemanggilan method tertentu pada objek. Method-method tersebut bernama special method atau dunder method (dari double underscore), dan dengan mengimplementasinya, class buatan kita ikut terhubung ke sintaks bawaan bahasa. Chapter ini memetakan mekanisme tersebut dan membangun class Playlist yang terasa seperti struktur data bawaan Python.
Data Model dan Lookup di Type #
Saat kita menulis len(daftar), interpreter Python tidak mencari atribut __len__ pada objek secara bebas — interpreter memanggil type(daftar).__len__(daftar). Pencarian dunder selalu terjadi pada tipe, bukan pada dictionary instance:
>>> daftar = [10, 20, 30]
>>> len(daftar)
3
>>> type(daftar).__len__(daftar)
3
>>> daftar.__len__()
3Tiga ekspresi tersebut menghasilkan nilai yang sama; len() di baris pertama adalah gula sintaksis untuk pemanggilan di baris kedua. Konsekuensi dari pencarian lewat tipe ini: dunder yang disimpan sebagai atribut instance diabaikan sepenuhnya.
class Lagu:
def __init__(self, judul):
self.judul = judul
lagu = Lagu("Rehat")
lagu.__len__ = lambda: 99 # tersimpan di __dict__ instance
print(lagu.__len__()) # pemanggilan eksplisit: bekerja
try:
print(len(lagu)) # len() hanya melihat type(lagu), bukan instance
except TypeError as e:
print("TypeError:", e)Output yang Dihasilkan:
99
TypeError: object of type 'Lagu' has no len()Pemanggilan eksplisit lagu.__len__() mengembalikan 99 karena ekspresi tersebut membaca atribut biasa dari __dict__ instance. Namun len(lagu) menolaknya dengan TypeError — fungsi len() hanya berkonsultasi ke type(lagu), dan class Lagu tidak mendefinisikan __len__. Aturan yang sama menjelaskan mengapa objek hasil dari Chapter Memahami Object dan Reference berperilaku konsisten: protokol bahasa melekat pada tipe.
Class Playlist dengan Special Methods #
Mari kita bangun class yang memanfaatkan data model secara penuh — sebuah Playlist yang bisa diukur, diindeks, diiterasi, dan dites keanggotaannya:
class Playlist:
def __init__(self, nama, lagu=None):
self.nama = nama
self.lagu = list(lagu) if lagu else []
def __repr__(self):
return f"Playlist({self.nama!r}, {len(self.lagu)} lagu)"
def __len__(self):
return len(self.lagu)
def __getitem__(self, indeks):
return self.lagu[indeks]
def __contains__(self, judul):
return any(judul == item for item in self.lagu)
def __iter__(self):
return iter(self.lagu)
favorit = Playlist("Santai Senja", ["Rehat", "Hujan Kemarin", "Senja Di Ambon"])
print(favorit)
print(len(favorit))
print(favorit[0])
print(favorit[-1])
print("Hujan Kemarin" in favorit)
print("Melompat" in favorit)
for judul in favorit:
print("-", judul)
if favorit:
print("playlist tidak kosong")Output yang Dihasilkan:
Playlist('Santai Senja', 3 lagu)
3
Rehat
Senja Di Ambon
True
False
- Rehat
- Hujan Kemarin
- Senja Di Ambon
playlist tidak kosongSetiap baris pada bagian bawah memicu satu dunder — inilah pemetaannya:
- Baris 24 (
print(favorit)):print()memanggil__str__; karena tidak didefinisikan, interpreter jatuh kembali ke__repr__dan mencetakPlaylist('Santai Senja', 3 lagu). - Baris 25 (
len(favorit)): diterjemahkan menjadiPlaylist.__len__(favorit)yang mengembalikan jumlah lagu. - Baris 26 (
favorit[0]): operator subscript[]memanggil__getitem__; karena method tersebut mendelegasikan ke list internal, indeks negatif sepertifavorit[-1]pada baris berikutnya pun bekerja. - Baris 28-29 (
in favorit): operatorinmemanggil__contains__— satu-satunya lagu yang tidak ada,"Melompat", dilaporkanFalse. - Baris 31 (
for judul in favorit): loopformemanggil__iter__untuk mendapatkan iterator. - Baris 34 (
if favorit:): pengujian truthiness memanggil__bool__; karena tidak ada, interpreter jatuh kembali ke__len__— panjang3berartiTrue.
Class Playlist kini terasa seperti struktur data asli Python: ia bisa masuk ke len(), in, for, dan bahkan sorted().
str vs repr #
Kedua dunder ini sama-sama menghasilkan teks, tetapi untuk dua tujuan berbeda. __repr__ menyasar pembangun — teks yang tepat untuk debugging, idealnya berupa ekspresi yang bisa direkonstruksi. __str__ menyasar pembaca — teks yang ramah untuk manusia:
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({self.judul!r}, {self.penulis!r})"
buku = Buku("Laskar Pelangi", "Andrea Hirata")
print(buku) # print() memakai __str__
print(repr(buku)) # repr() memakai __repr__
print([buku]) # container memakai __repr__ elemennya
print(f"{buku!r}") # format spec !r memaksa repr()Output yang Dihasilkan:
Laskar Pelangi oleh Andrea Hirata
Buku('Laskar Pelangi', 'Andrea Hirata')
[Buku('Laskar Pelangi', 'Andrea Hirata')]
Buku('Laskar Pelangi', 'Andrea Hirata')Perhatikan dua baris terakhir: elemen di dalam container selalu dirender dengan __repr__, dan conversion spec !r pada f-string memaksa representasi penuh. Jika hanya ada satu yang sempat ditulis, pilih __repr__ — ia juga menjadi fallback untuk __str__ yang hilang.
Objek yang Bisa Dipanggil dan Tabel Dunder #
Dunder __call__ membuat objek bisa dipanggil layaknya function — pola untuk objek berstatus yang menyimpan konteks antar pemanggilan:
class Pemutar:
"""Contoh __call__: objek yang dipanggil layaknya function."""
def __init__(self, lagu):
self.lagu = list(lagu)
self.posisi = 0
def __call__(self):
lagu = self.lagu[self.posisi % len(self.lagu)]
self.posisi += 1
return lagu
putar = Pemutar(["Rehat", "Hujan Kemarin"])
print(putar())
print(putar())
print(putar())Output yang Dihasilkan:
Rehat
Hujan Kemarin
RehatEkspresi putar() memanggil Pemutar.__call__(putar); objek putar menyimpan posisi lagu di antara pemanggilan sehingga urutan berputar kembali ke awal. Berikut dunder yang paling sering dijumpai:
| Special method | Dipicu oleh | Peran |
|---|---|---|
__init__ | Produk(...) | inisialisasi objek baru |
__repr__ | repr(obj), container, REPL | representasi untuk debugging |
__str__ | print(obj), f-string | representasi untuk manusia |
__len__ | len(obj) | jumlah elemen |
__bool__ | if obj: | konversi truthy/falsy |
__getitem__ | obj[k] | pembacaan indeks/kunci |
__setitem__ | obj[k] = v | penulisan indeks/kunci |
__iter__ | for x in obj | protokol iterasi |
__contains__ | x in obj | tes keanggotaan |
__call__ | obj() | objek sebagai function |
__eq__, __lt__ | ==, < | perbandingan |
__hash__ | hash(obj) | kunci dict/set |
Dunder untuk operator aritmetika dan perbandingan mendapat pembahasan khusus pada Chapter Operator Overloading.