Pendahuluan — mewarisi tanpa menyalin-tempel

Di Encapsulation & @property (#43) class Buku sudah punya state terlindungi. Hari ini kita buat variasi tanpa menduplikasi seluruh class: ebook, audiobook, atau edisi khusus yang “mirip buku” tapi punya data ekstra.

Inheritance (pewarisan) = class anak mewarisi attribute & method class induk, lalu boleh menambah atau meng-override yang perlu. Di Python: class Ebook(Buku):.

Catatan: di artikel ini Buku disederhanakan (attribute publik, tanpa @property) supaya pola super() dan override tidak tertutup detail validasi. Di proyek nyata, inheritance + encapsulation boleh digabung.

Prasyarat: Selesai Encapsulation (#43) (atau setidaknya nyaman dengan class, __init__, method). Fondasi: Attribute & __init__ (#42) · Class & Object (#41) · Mengenal OOP (#40).

Kenapa inheritance?

Tanpa pewarisan, kamu cenderung copy-paste Buku jadi Ebook, lalu bug perbaikan harus diubah di dua tempat. Dengan inheritance:

  • Aturan bersama (mis. pinjam()) tinggal di induk
  • Anak hanya menambah yang beda (format file, ukuran MB)
  • Override method kalau perilaku perlu berbeda (mis. teks info())

Jangan warisi hanya demi “hemat mengetik” — warisi karena memang adalah-sejenis (ebook adalah buku digital). Pola “punya” daftar object lain menunggu Composition (nanti di Seri 3).

Class induk Buku (titik berangkat)

Kita pakai versi ringkas yang fokus ke pewarisan. Validasi property tetap boleh seperti di Encapsulation (#43); di sini disederhanakan agar pola super() terlihat jelas:

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}"

    def pinjam(self):
        if self.stok <= 0:
            raise ValueError("stok habis")
        self.stok -= 1
        return self.stok


b = Buku("Belajar Python", "Sari", 2024, stok=2)
print(b.info())

class Ebook(Buku) — anak mewarisi induk

Sintaks: nama induk di dalam kurung. Instance Ebook bisa memakai method induk langsung:

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}"

    def pinjam(self):
        if self.stok <= 0:
            raise ValueError("stok habis")
        self.stok -= 1
        return self.stok


class Ebook(Buku):
    """Ebook adalah Buku + metadata file."""
    pass


e = Ebook("Belajar Python", "Sari", 2024, stok=1)
print(e.info())          # diwarisi dari Buku
print(e.pinjam())        # juga diwarisi
print(isinstance(e, Ebook))  # True
print(isinstance(e, Buku))   # True — ebook juga "adalah" Buku

pass di tubuh class berarti “belum menambah apa-apa”. Berguna untuk memahami: anak kosong tetap bisa lahir lewat __init__ induk.

super().__init__ — menyiapkan state induk

Kalau anak punya __init__ sendiri, method itu menggantikan __init__ induk — Python tidak memanggil induk otomatis. Karena itu kamu hampir selalu perlu memanggil __init__ induk lewat super() agar attribute dasar ikut terisi:

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


e = Ebook("Belajar Python", "Sari", 2024, stok=1, format_file="pdf", ukuran_mb=4.5)
print(e.judul, e.format_file, e.ukuran_mb)
print(e.info())  # masih pakai info() milik Buku

Tanpa super().__init__(...), attribute seperti judul / stok sering belum ada — lalu muncul AttributeError saat method induk dijalankan. Bandingkan pola SALAH vs BENAR:

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}"


# SALAH — anak punya __init__ tapi lupa super()
class EbookSalah(Buku):
    def __init__(self, judul, penulis, tahun, stok=1, format_file="epub"):
        # lupa: super().__init__(judul, penulis, tahun, stok)
        self.format_file = format_file


# e = EbookSalah("Belajar Python", "Sari", 2024)  # object lahir
# print(e.info())  # AttributeError: 'EbookSalah' object has no attribute 'judul'


# BENAR
class EbookBenar(Buku):
    def __init__(self, judul, penulis, tahun, stok=1, format_file="epub"):
        super().__init__(judul, penulis, tahun, stok)
        self.format_file = format_file


e = EbookBenar("Belajar Python", "Sari", 2024, format_file="pdf")
print(e.judul, e.format_file)
print(e.info())

Ingat: super() mencari implementasi di rantai induk (MRO). Untuk kasus satu induk seperti ini, anggap saja “panggil versi Buku”.

Override method info()

Anak boleh menulis ulang method dengan nama sama. Versi anak yang dipanggil saat object-nya Ebook:

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):
        dasar = super().info()  # manfaatkan teks induk
        return f"{dasar} · {self.format_file.upper()} ({self.ukuran_mb} MB)"


b = Buku("ESP32 Praktis", "Budi", 2023)
e = Ebook("Belajar Python", "Sari", 2024, format_file="pdf", ukuran_mb=4.5)
print(b.info())
print(e.info())

Pola sehat: override untuk melengkapi, bukan menyalin ulang seluruh logika induk. super().info() menjaga satu sumber kebenaran untuk bagian yang sama. Object Buku tetap memakai versi induk; object Ebook memakai versi anak.

Di dalam method anak, self tetap object Ebook — itu kenapa self.format_file tersedia setelah super().info(). Kalau kamu menulis ulang info() dari nol tanpa super(), teks dasar induk ikut terduplikasi; perbaikan format di Buku.info() tidak otomatis ikut ke anak.

Buku (induk) __init__ · info · pinjam Ebook (anak) + format_file · override info Audiobook (latihan) + durasi_menit · override info mewarisi
Satu induk, banyak anak. Ebook dibahas di artikel; Audiobook kamu kerjakan di latihan.

Pola Dasar — merancang inheritance

  1. 1
    Pastikan “adalah-sejenis” Ebook adalah Buku. Kalau hanya “punya” buku, jangan inheritance.
  2. 2
    Tulis class Anak(Induk) class Ebook(Buku): — nama induk di kurung.
  3. 3
    Panggil super().__init__ Isi state bersama dulu, baru attribute khusus anak.
  4. 4
    Override secukupnya Ubah info() / perilaku yang benar-benar beda; sisanya warisi.
  5. 5
    Uji induk dan anak Pastikan Buku lama tidak rusak; isinstance(e, Buku) masuk akal.
Ringkas: adalah-sejenis → Anak(Induk) → super() → override → uji.

Kode lengkap — salin & jalankan

Simpan sebagai perpustakaan_ebook.py, lalu python perpustakaan_ebook.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}"

    def pinjam(self):
        if self.stok <= 0:
            raise ValueError("stok habis")
        self.stok -= 1
        return 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)"


b = Buku("ESP32 Praktis", "Budi", 2023, stok=2)
e = Ebook("Belajar Python", "Sari", 2024, stok=1, format_file="pdf", ukuran_mb=4.5)

print(b.info())
print(e.info())
print("pinjam ebook:", e.pinjam())
print("isinstance ebook→Buku:", isinstance(e, Buku))
print("type(e) is Buku:", type(e) is Buku)  # False — tipe persis tetap Ebook

Output yang diharapkan (kurang lebih):

ESP32 Praktis · Budi (2023) · stok 2
Belajar Python · Sari (2024) · stok 1 · PDF (4.5 MB)
pinjam ebook: 0
isinstance ebook→Buku: True
type(e) is Buku: False

Kesalahan umum

Gejala Penyebab tipikal Perbaikan
AttributeError: 'Ebook' object has no attribute 'judul' Anak punya __init__ tapi lupa super().__init__(...) Panggil super().__init__(...) sebelum pakai attribute induk
TypeError: Buku.__init__() missing ... dari anak Argumen ke super().__init__ kurang / tertukar Samakan urutan parameter dengan induk
Override menghilangkan perilaku induk Menulis ulang info() tanpa super().info() Panggil super() lalu tambah detail anak
Warisan terasa dipaksakan Anak tidak benar-benar “adalah” induk (hanya reuse field) Pertimbangkan composition (nanti) atau class terpisah
Bingung isinstance vs tipe persis Mengira hanya type(e) is Buku isinstance(e, Buku) True untuk anak; detail polimorfisme di artikel berikutnya

Latihan singkat

  1. Buat class Audiobook(Buku) dengan attribute durasi_menit. Override info() supaya menampilkan durasi.
  2. Di Ebook.__init__, sengaja hapus super().__init__ sebentar — amati error, lalu kembalikan.
  3. Buat list [b, e] dan cetak item.info() untuk tiap item. (Ini cuplikan Polymorphism — dibahas penuh di artikel berikutnya.)

FAQ singkat

Apakah anak otomatis mewarisi semua method?
Ya, kecuali di-override. Attribute instance tetap lahir lewat __init__ (biasanya lewat super()).

Apakah pinjam() harus ditulis ulang di Ebook?
Tidak, kalau perilakunya sama. Warisi saja. Override hanya jika ebook butuh aturan berbeda (mis. stok digital tak terbatas — itu keputusan desain nanti).

Bolehkah banyak induk (multiple inheritance)?
Boleh di Python, tapi untuk pemula: tahan dulu. Satu induk sudah cukup untuk fondasi.

Haruskah selalu override info()?
Tidak. Override hanya jika teks/perilaku induk kurang tepat untuk anak.

Kenapa di sini Buku tanpa @property?
Supaya fokus ke pewarisan. Setelah nyaman dengan super(), gabungkan lagi dengan pola Encapsulation (#43).

Apakah __nama (name-mangling) ikut “rusak” di anak?
Python mengubah __nama jadi _NamaClass__nama menurut class yang mendefinisikan-nya, jadi bentrok nama di subclass jarang terjadi. Untuk pemula: tetap utamakan _ seperti di #43; __ jarang diperlukan.

Kesimpulan & langkah berikutnya

Inheritance memakai hubungan “adalah-sejenis”: anak mewarisi induk, mengisi state lewat super().__init__, dan meng-override method bila perlu. Kamu menghindari copy-paste sekaligus menjaga satu tempat untuk aturan bersama.

Artikel ini adalah #44 (ini) — langkah kelima Seri 3 setelah Encapsulation (#43).

Lanjut ke artikel berikutnya — Polymorphism: daftar campuran Buku / Ebook diproses lewat interface yang sama, plus kapan isinstance boleh dipakai. (Belum di-hyperlink sampai artikel itu live.)

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