Pada akhir Chapter Inheritance ada fakta penting: object BukuReferensi terhitung sebagai instance dari Buku. Fakta itu menjadi sangat berguna ketika digabungkan dengan satu teknik: memanggil method yang sama pada object-object berbeda dan mendapatkan perilaku berbeda-beda. Teknik inilah polymorphism — “banyak bentuk” — dan diam-diam sudah kita pakai sejak awal belajar Python: len() bekerja untuk string, list, sekaligus dictionary. Chapter ini membuktikan bahwa polymorphism bukan fitur yang harus diaktifkan, melainkan konsekuensi alami dari cara Python mencari method.

Satu Interface, Banyak Perilaku #

Perhatikan fungsi perlihatkan_suara() berikut — sementara kita kembali ke hewan agar intinya terlihat jernih:

python
class Kucing:
    def bersuara(self):
        return "Meong"

class Anjing:
    def bersuara(self):
        return "Guk guk"

class Sapi:
    def bersuara(self):
        return "Moo"

def perlihatkan_suara(hewan):
    print(hewan.bersuara())

koleksi_hewan = [Kucing(), Anjing(), Sapi()]

for hewan in koleksi_hewan:
    perlihatkan_suara(hewan)

Output yang Dihasilkan:

text
Meong
Guk guk
Moo

Mari kita telaah cara kerjanya:

  1. Fungsi perlihatkan_suara(hewan): parameter hewan tidak diperiksa tipenya. Fungsi hanya butuh satu janji — object yang datang punya method bersuara().
  2. Baris print(hewan.bersuara()): pada tiap iterasi, interpreter mencari method bersuara pada class milik object yang sedang diproses, lalu menjalankan versi milik class itu.
  3. List campuran: satu list menampung tiga class berbeda dan loop tetap berjalan tanpa percabangan if bertipe.

Tiga class di atas bahkan tidak punya induk yang sama. Bandingkan dengan versi berbasis pewarisan — di domain perpustakaan — di mana durasi_pinjam() di-override oleh tiap anak:

python
class ItemPerpustakaan:
    def __init__(self, judul):
        self.judul = judul

    def durasi_pinjam(self):
        return 7

class Buku(ItemPerpustakaan):
    def durasi_pinjam(self):
        return 14

class Majalah(ItemPerpustakaan):
    def durasi_pinjam(self):
        return 3

item_pinjaman = [Buku("Laskar Pelangi"), Majalah("National Geographic")]

for item in item_pinjaman:
    print(f"{item.judul}: pinjam {item.durasi_pinjam()} hari")

Output yang Dihasilkan:

text
Laskar Pelangi: pinjam 14 hari
National Geographic: pinjam 3 hari

Method durasi_pinjam() versi induk menjadi nilai bawaan; tiap anak menimpanya dengan aturan sendiri, dan pemanggil tidak pernah perlu tahu class mana yang sedang menjawab. Override yang kita pelajari di Chapter Inheritance baru benar-benar berbunga di sini.

Duck Typing #

Bagaimana jika dua contoh di atas bekerja tanpa inheritance sama sekali? Ternyata tetap bekerja — dan memang begitulah Python:

python
class Anjing:
    def bersuara(self):
        return "Guk guk"

class Alarm:
    def bersuara(self):
        return "Kring kring!"

def perlihatkan_suara(benda):
    print(benda.bersuara())

perlihatkan_suara(Anjing())
perlihatkan_suara(Alarm())

Output yang Dihasilkan:

text
Guk guk
Kring kring!

Fungsi perlihatkan_suara() dengan santai menerima Alarm — object yang jelas bukan hewan — karena satu-satunya syarat adalah method bersuara(). Prinsip ini disebut duck typing, dari ungkapan “jika berjalan seperti bebek dan bersuara seperti bebek, ia adalah bebek”. Python tidak memeriksa class si pemanggil, hanya kemampuannya. Konsekuensinya dua arah: kode menjadi sangat fleksibel, tetapi object yang tidak punya method yang dijanjikan akan gagal dengan AttributeError — penanganannya sudah dibahas pada Section Exception Handling.

Duck typing juga menuntut kedisiplinan kecil dari sisi penulis class: nama method adalah kontrak publik. bersuara() dipilih dan dijaga konsisten lintas class sehingga pemanggil bisa bekerja tanpa peduli implementasi; menamainya suara() di satu class dan bersuara() di class lain justru mematahkan kontrak itu. Kebutuhan menuliskan kontrak seperti ini secara eksplisit — bukan hanya lewat kebiasaan penamaan — adalah salah satu alasan type hints menjadi menarik; pembahasannya menyusul pada Section Type Hints dan Static Analysis.

Protokol Bawaan Python: len() #

Polymorphism bukan monopoli method buatan kita — seluruh bahasa berdiri di atasnya. Ambil fungsi len() yang sudah akrab:

python
print(len("Laskar Pelangi"))
print(len(["Fiksi", "Biografi", "Sains"]))
print(len({"judul": "Bumi Manusia", "tahun": 1980}))

Output yang Dihasilkan:

text
14
3
2

Satu fungsi, tiga class berbeda, tiga hasil. Mekanismenya: len() memanggil method khusus __len__ milik object tersebut. Class str mendefinisikan __len__ untuk menghitung karakter, list untuk menghitung elemen. Class kita sendiri bisa ikut bermain — cukup definisikan __len__:

python
class RakBuku:
    def __init__(self):
        self.isi_rak = []

    def tambah(self, judul_buku):
        self.isi_rak.append(judul_buku)

    def __len__(self):
        return len(self.isi_rak)

rak = RakBuku()
rak.tambah("Laskar Pelangi")
rak.tambah("Bumi Manusia")
rak.tambah("Negeri 5 Menara")

print(len(rak))

Output yang Dihasilkan:

text
3

Fungsi len(rak) menemukan __len__ pada class RakBuku dan menjalankannya — rak buku mendapat makna “panjang” versi sendiri: jumlah buku di dalamnya. Pola method khusus ini persis keluarga __init__ dan __repr__ dari Chapter Instance Methods; daftar lengkapnya dan cara memakainya dibahas pada chapter Special Methods dan Data Model di Section Advanced Data Modeling.