Pendahuluan — satu pemanggilan, banyak bentuk

Di Inheritance & super() (#44) kamu sudah punya Buku dan Ebook dengan info() yang berbeda. Hari ini kita manfaatkan itu: satu loop memanggil item.info(), tiap object merespons sesuai bentuknya.

Polymorphism (banyak bentuk) = object berbeda merespons pesan/method yang sama dengan cara masing-masing. Di Python, ini sering tampak sederhana: “kalau object punya info(), panggil saja.”

Prasyarat: Selesai Inheritance (#44) (nyaman dengan Ebook(Buku), super(), override). Fondasi: Encapsulation (#43) · Attribute (#42) · Class & Object (#41) · Mengenal OOP (#40).

Kenapa polymorphism terasa “ajaib”?

Tanpa polymorphism, katalog campuran cepat jadi hutan if/isinstance. Dengan polymorphism:

  • Kode pemanggil tetap pendek: for item in koleksi: print(item.info())
  • Perilaku spesifik tinggal di class masing-masing (override)
  • Menambah tipe baru (mis. Audiobook) sering tidak memaksa ubah loop

Intinya: satu antarmuka, banyak implementasi.

Siapkan Buku, Ebook, Audiobook

Versi ringkas lanjutan Inheritance (#44) — fokus ke info() yang berbeda per class:

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

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


class Ebook(Buku):
    def __init__(self, judul, penulis, tahun, stok=1, format_file="epub", ukuran_mb=1.0):
        super().__init__(judul, penulis, tahun, stok)
        self.format_file = format_file
        self.ukuran_mb = ukuran_mb

    def info(self):
        return f"{super().info()} · {self.format_file.upper()} ({self.ukuran_mb} MB)"


class Audiobook(Buku):
    def __init__(self, judul, penulis, tahun, stok=1, durasi_menit=60):
        super().__init__(judul, penulis, tahun, stok)
        self.durasi_menit = durasi_menit

    def info(self):
        return f"{super().info()} · audio {self.durasi_menit} menit"


b = Buku("ESP32 Praktis", "Budi", 2023)
e = Ebook("Belajar Python", "Sari", 2024, format_file="pdf", ukuran_mb=4.5)
a = Audiobook("Cerita Sensor", "Ani", 2025, durasi_menit=90)
print(b.info())
print(e.info())
print(a.info())

Satu loop untuk semua — inti polymorphism

Masukkan object berbeda ke satu list, lalu panggil method yang sama:

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

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


class Ebook(Buku):
    def __init__(self, judul, penulis, tahun, stok=1, format_file="epub", ukuran_mb=1.0):
        super().__init__(judul, penulis, tahun, stok)
        self.format_file = format_file
        self.ukuran_mb = ukuran_mb

    def info(self):
        return f"{super().info()} · {self.format_file.upper()} ({self.ukuran_mb} MB)"


class Audiobook(Buku):
    def __init__(self, judul, penulis, tahun, stok=1, durasi_menit=60):
        super().__init__(judul, penulis, tahun, stok)
        self.durasi_menit = durasi_menit

    def info(self):
        return f"{super().info()} · audio {self.durasi_menit} menit"


koleksi = [
    Buku("ESP32 Praktis", "Budi", 2023),
    Ebook("Belajar Python", "Sari", 2024, format_file="pdf", ukuran_mb=4.5),
    Audiobook("Cerita Sensor", "Ani", 2025, durasi_menit=90),
]

for item in koleksi:
    print(item.info())  # tiap class menjawab dengan caranya sendiri

Loop tidak perlu tahu “ini Ebook atau Audiobook”. Yang penting: tiap item paham cara menjawab info(). Python mencari method di tipe object yang sebenarnya — object Ebook menjalankan Ebook.info, bukan versi induk.

Output kurang lebih:

ESP32 Praktis · Budi (2023) · stok 1
Belajar Python · Sari (2024) · stok 1 · PDF (4.5 MB)
Cerita Sensor · Ani (2025) · stok 1 · audio 90 menit
for item in koleksi item.info() Buku info dasar Ebook + format / MB Audiobook + durasi
Satu pesan info(), tiga jawaban berbeda — itu polymorphism di kerja sehari-hari.

Duck typing — “kalau bisa quack…”

Python tidak selalu menuntut warisan formal. Object di luar pohon Buku pun bisa ikut loop jika punya method yang dibutuhkan:

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

    def info(self):
        return f"Buku: {self.judul}"


class KatalogEntry:
    """Bukan subclass Buku — tapi punya info()."""
    def __init__(self, judul, sumber):
        self.judul = judul
        self.sumber = sumber

    def info(self):
        return f"Entri katalog: {self.judul} ({self.sumber})"


koleksi = [
    Buku("ESP32 Praktis"),
    KatalogEntry("Datasheet DHT22", "vendor"),
]

for item in koleksi:
    print(item.info())  # jalan — keduanya “bisa di-info()”

Ini sering disebut duck typing: yang diuji kemampuan (info()), bukan silsilah class. Inheritance tetap berguna untuk berbagi state/perilaku; duck typing berguna saat kontrak method-nya kecil dan jelas.

Risikonya: object yang “kelihatan cocok” tapi tanpa method yang diharapkan gagal saat runtime:

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

    def info(self):
        return f"Buku: {self.judul}"


koleksi = [Buku("ESP32 Praktis"), {"judul": "bukan object"}]
# for item in koleksi:
#     print(item.info())  # AttributeError: 'dict' object has no attribute 'info'
print(koleksi[0].info())

Ingat: duck typing fleksibel, tapi error muncul saat runtime jika method hilang (AttributeError). Untuk kontrak ketat antar banyak class, lihat Abstraction & ABC (#46).

isinstance — kapan boleh, kapan berlebihan

isinstance berguna untuk pengecualian, bukan sebagai pengganti polymorphism. Bandingkan SALAH vs BENAR:

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

    def info(self):
        return f"Buku: {self.judul}"


class Ebook(Buku):
    def __init__(self, judul, format_file="pdf"):
        super().__init__(judul)
        self.format_file = format_file

    def info(self):
        return f"Ebook: {self.judul} · {self.format_file}"

    def unduh(self):
        return f"unduh {self.judul}.{self.format_file}"


# SALAH — hutan isinstance menggantikan method bersama
# (urutan Ebook→Buku di sini sudah “aman”; yang salah adalah pola hutannya)
def cetak_salah(koleksi):
    for item in koleksi:
        if isinstance(item, Ebook):
            print(f"Ebook: {item.judul} · {item.format_file}")
        elif isinstance(item, Buku):
            print(f"Buku: {item.judul}")
        else:
            print("tipe tidak dikenal")


# BENAR — andalkan info(); isinstance hanya untuk aksi khusus
def cetak_benar(koleksi):
    for item in koleksi:
        print(item.info())
        if isinstance(item, Ebook):
            print("  →", item.unduh())  # hanya Ebook yang punya unduh()


koleksi = [Buku("ESP32 Praktis"), Ebook("Belajar Python", "pdf")]
cetak_benar(koleksi)
# cetak_salah(koleksi)  # anti-pola — jangan dijadikan kebiasaan

Pakai isinstance saat ada kemampuan yang memang hanya milik sebagian tipe (mis. unduh() di ebook). Jangan pakai untuk meniru seluruh info() yang seharusnya di-override.

Jebakan urutan: karena isinstance(ebook, Buku) juga True (lihat Inheritance (#44)), rantai yang cek induk dulu menelan ebook:

class Buku:
    pass


class Ebook(Buku):
    pass


e = Ebook()
if isinstance(e, Buku):
    print("cabang Buku")      # kena — Ebook juga Buku
elif isinstance(e, Ebook):
    print("cabang Ebook")     # tidak pernah tercapai

Output: cabang Buku. Kalau terpaksa menulis rantai, cek tipe anak dulu — atau lebih baik, andalkan method bersama seperti di cetak_benar. Catatan: di contoh cetak_salah urutan Ebook→Buku sudah tepat; yang dilarang adalah mengganti info() dengan hutan isinstance.

Pola Dasar — merancang polymorphism

  1. 1
    Samakan nama method Mis. semua item katalog punya info() — antarmuka bersama.
  2. 2
    Override di anak (bila warisan) Detail spesifik tinggal di class; pemanggil tidak perlu tahu tipenya.
  3. 3
    Loop lewat antarmuka for item in koleksi: item.info() — bukan rantai isinstance.
  4. 4
    isinstance hanya pengecualian Untuk aksi yang benar-benar khas satu tipe (unduh, play, dll.).
  5. 5
    Uji tipe baru Tambah class baru ke list — loop lama sebaiknya tetap jalan tanpa diubah.
Ringkas: antarmuka sama → override → loop → isinstance jarang → uji tipe baru.

Kode lengkap — salin & jalankan

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

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

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


class Ebook(Buku):
    def __init__(self, judul, penulis, tahun, stok=1, format_file="epub", ukuran_mb=1.0):
        super().__init__(judul, penulis, tahun, stok)
        self.format_file = format_file
        self.ukuran_mb = ukuran_mb

    def info(self):
        return f"{super().info()} · {self.format_file.upper()} ({self.ukuran_mb} MB)"

    def unduh(self):
        return f"unduh {self.judul}.{self.format_file}"


class Audiobook(Buku):
    def __init__(self, judul, penulis, tahun, stok=1, durasi_menit=60):
        super().__init__(judul, penulis, tahun, stok)
        self.durasi_menit = durasi_menit

    def info(self):
        return f"{super().info()} · audio {self.durasi_menit} menit"


class KatalogEntry:
    def __init__(self, judul, sumber):
        self.judul = judul
        self.sumber = sumber

    def info(self):
        return f"Entri katalog: {self.judul} ({self.sumber})"


koleksi = [
    Buku("ESP32 Praktis", "Budi", 2023, stok=2),
    Ebook("Belajar Python", "Sari", 2024, format_file="pdf", ukuran_mb=4.5),
    Audiobook("Cerita Sensor", "Ani", 2025, durasi_menit=90),
    KatalogEntry("Datasheet DHT22", "vendor"),
]

for item in koleksi:
    print(item.info())
    if isinstance(item, Ebook):
        print("  →", item.unduh())

print("isinstance ebook→Buku:", isinstance(koleksi[1], Buku))
print("type(katalog) is Buku:", type(koleksi[3]) is Buku)

Output yang diharapkan (kurang lebih):

ESP32 Praktis · Budi (2023) · stok 2
Belajar Python · Sari (2024) · stok 1 · PDF (4.5 MB)
  → unduh Belajar Python.pdf
Cerita Sensor · Ani (2025) · stok 1 · audio 90 menit
Entri katalog: Datasheet DHT22 (vendor)
isinstance ebook→Buku: True
type(katalog) is Buku: False

Kesalahan umum

Gejala Penyebab tipikal Perbaikan
Loop penuh isinstance panjang Menyalin perilaku ke pemanggil, bukan ke class Pindahkan ke method bersama (info()) / override
AttributeError: ... has no attribute 'info' Object di list tidak punya method yang diharapkan Samakan antarmuka, atau jangan masukkan tipe itu ke koleksi
Tipe baru memaksa ubah semua if Polymorphism diganti cabang tipe Tambah class + method; biarkan loop lama
Bingung duck typing vs inheritance Mengira harus selalu Anak(Induk) Warisi bila berbagi state/perilaku; duck typing OK untuk kontrak kecil
isinstance “salah cabang” Cek anak setelah induk dengan urutan terbalik Cek tipe lebih spesifik dulu, atau hindari hutan isinstance

Latihan singkat

  1. Tambah class Majalah(Buku) dengan edisi, override info(), masukkan ke koleksi — pastikan loop lama tidak diubah.
  2. Sengaja masukkan object tanpa info() ke list — amati AttributeError, lalu perbaiki.
  3. Refactor satu fungsi yang penuh isinstance menjadi pemanggilan method bersama.

FAQ singkat

Apakah polymorphism wajib pakai inheritance?
Tidak. Inheritance memudahkan berbagi kode; duck typing cukup jika method-nya sama. Keduanya sah di Python.

Kapan isinstance masih boleh?
Saat ada aksi yang memang khas satu tipe dan belum layak digabung ke antarmuka bersama.

Apakah type(x) is Buku sama dengan isinstance?
Tidak. type(x) is Buku menolak anak class. Untuk “adalah Buku atau turunannya”, pakai isinstance — detailnya sudah muncul di Inheritance (#44).

Kenapa isinstance(ebook, Buku) True?
Karena pewarisan: ebook adalah Buku. Itu kekuatan polymorphism — dan sekaligus alasan hutan isinstance mudah salah urutan.

Bagaimana memaksa semua class punya method tertentu?
Itu ranah Abstraction & ABC (#46).

Kesimpulan & langkah berikutnya

Polymorphism membuat pemanggil tetap sederhana: panggil antarmuka bersama, biarkan tiap class menjawab dengan caranya. Hindari hutan isinstance; cadangkan untuk pengecualian yang benar-benar khas.

Artikel ini adalah #45 (ini) — langkah keenam Seri 3 setelah Inheritance (#44).

Lanjut ke Abstraction & ABC (#46): kontrak class abstract supaya subclass wajib mengimplementasikan method tertentu.

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