Inheritance (pewarisan) membuat class baru dari class yang sudah ada: class baru mewarisi semua atribut dan method induknya, lalu boleh menambah atau mengganti sebagian. Bayangin Pak Darto mau bikin kartu khusus minuman. Dia gak merancang kartu dari nol. Dia ambil blanko kartu menu yang lama (nama, harga), lalu menambah satu kolom: "pakai es?". Semua yang ada di kartu menu tetap ada di kartu minuman.
🧪 Tebakan cepat:
MenuMinumanpunya__init__sendiri yang cuma menulisself.es = es, tanpasuper().__init__(...). Setelahteh = MenuMinuman("teh manis", 5000, es=True), apa hasilprint(teh.nama):teh manis, atau error? Tebak dulu, jawabannya ada di bagian "super(): memanggil versi induk".
Class turunan
Kita pakai class Menu dari pelajaran sebelumnya:
class Menu:
def __init__(self, nama, harga):
self.nama = nama
self.harga = harga
def total(self, jumlah):
return self.harga * jumlah
def __str__(self):
return f"{self.nama} ({self.harga})"
class MenuMinuman(Menu):
pass
teh = MenuMinuman("teh manis", 5000)
print(teh) # teh manis (5000)
print(teh.total(4)) # 20000
class MenuMinuman(Menu):artinyaMenuMinumanturunan dariMenu. Class di dalam kurung disebut induk (parent class atau superclass), class barunya disebut anak (child class atau subclass).passartinya badan class-nya kosong. Walau kosong,MenuMinumansudah punya__init__,total, dan__str__, semuanya warisan dariMenu.MenuMinuman("teh manis", 5000)menjalankan__init__milikMenu, karena anaknya gak punya sendiri.
Menambah dan mengganti method
Anak boleh menulis method dengan nama yang sama seperti milik induknya. Untuk object anak, method itu menggantikan milik induk. Ini disebut override. Di warung Pak Darto, minuman pakai es lebih mahal Rp2.000 per gelas:
class MenuMinuman(Menu):
def __init__(self, nama, harga, es=False):
super().__init__(nama, harga)
self.es = es
def total(self, jumlah):
hasil = super().total(jumlah)
if self.es:
hasil = hasil + 2000 * jumlah
return hasil
teh = MenuMinuman("teh manis", 5000, es=True)
kopi = MenuMinuman("kopi susu", 15000)
roti = Menu("roti bakar", 12000)
print(teh.total(4)) # 28000
print(kopi.total(3)) # 45000
print(roti.total(2)) # 24000
print(teh) # teh manis (5000)
| Panggilan | total milik |
Hitungan | Hasil |
|---|---|---|---|
teh.total(4) |
MenuMinuman |
5000 × 4 = 20000, ditambah es 2000 × 4 = 8000 | 28000 |
kopi.total(3) |
MenuMinuman |
15000 × 3, es bernilai False |
45000 |
roti.total(2) |
Menu |
12000 × 2 | 24000 |
__init__anak menerima satu parameter tambahan,es, dengan defaultFalse.kopidibuat tanpaes, jadi gak kena tambahan.totaldi anak meng-overridetotaldi induk, tapi cuma untuk objectMenuMinuman.rotiobjectMenubiasa, jadi tetap memakaitotalmilikMenu.__str__gak ditulis ulang, jadiprint(teh)tetap memakai warisan dariMenu.
super(): memanggil versi induk
super() memberi akses ke method milik induk. Ada dua pemakaian di kode tadi:
super().__init__(nama, harga)menjalankan__init__milikMenu, yang mengisiself.namadanself.harga. Baru setelah itu anaknya menambahself.es. Perhatikan:selfgak ikut dikirim.super().total(jumlah)memanggiltotalmilikMenuuntuk harga dasarnya, jadi rumusself.harga * jumlahgak perlu ditulis dua kali.
Kalau anak punya __init__ sendiri, __init__ milik induk gak jalan otomatis. Tanpa baris super().__init__(nama, harga), object teh gak pernah dapat nama dan harga:
Traceback (most recent call last):
File "/home/darto/warung.py", line 17, in <module>
print(teh.nama)
^^^^^^^^
AttributeError: 'MenuMinuman' object has no attribute 'nama'
Jadi hasilnya error, bukan teh manis. Itu jawaban tebakannya.
isinstance dan urutan pencarian (MRO)
print(isinstance(teh, MenuMinuman)) # True
print(isinstance(teh, Menu)) # True
print(isinstance(roti, MenuMinuman)) # False
print(type(teh) == Menu) # False
print(MenuMinuman.__mro__)
Baris terakhir mencetak:
(<class '__main__.MenuMinuman'>, <class '__main__.Menu'>, <class 'object'>)
isinstance(obj, Class)bernilaiTruekalauobjdibuat dari class itu atau turunannya. Minuman tetap sebuah menu.type(teh) == MenubernilaiFalse, karenatypecuma melihat class persisnya. Untuk mengecek "apakah ini sebuahMenu", pakaiisinstance.__mro__(method resolution order) adalah urutan tempat Python mencari atribut dan method: class object itu dulu, lalu induknya, terus naik sampaiobject, induk dari semua class. Yang pertama ketemu, itu yang dipakai.- Itulah cara override bekerja:
teh.totalsudah ketemu diMenuMinuman, jaditotalmilikMenugak tersentuh.__str__gak ada diMenuMinuman, jadi Python naik keMenu. objectjuga sumber__str__bawaan yang mencetak<__main__.Menu object at 0x...>saat class-mu gak menulis__str__.
📌 Induk lebih dari satu
Class boleh punya beberapa induk: class D(B, C):. MRO-nya dibaca dari kiri ke kanan, dan induk bersama (A) baru dicek setelah semua anaknya:
class A:
def hai(self):
return "A"
class B(A):
def hai(self):
return "B"
class C(A):
def hai(self):
return "C"
class D(B, C):
pass
print(D().hai()) # B
print([k.__name__ for k in D.__mro__]) # ['D', 'B', 'C', 'A', 'object']
D gak punya hai, jadi Python mencari di B dulu dan berhenti di sana. Pola ini jarang dibutuhkan pemula. Satu induk sudah cukup untuk hampir semua kasus.
⚠️ Jebakan yang sering ketemu
- Lupa
super().__init__(...).__init__anak menggantikan milik induk, jadi atribut induk gak pernah dibuat:AttributeError: 'MenuMinuman' object has no attribute 'nama'. - Ikut mengirim
selfkesuper().super().__init__(self, nama, harga)ditolak:TypeError: Menu.__init__() takes 3 positional arguments but 4 were given.super()sudah membawaself. - Argumen
super().__init__kurang.super().__init__(nama)gagal:TypeError: Menu.__init__() missing 1 required positional argument: 'harga'. - Turunan untuk hubungan "punya". Pakai inheritance kalau anaknya adalah induknya: minuman adalah menu.
Pesananpunya banyak menu, tapi pesanan bukan menu, jadi simpan menunya sebagai atribut, bukanclass Pesanan(Menu).
TL;DR: class MenuMinuman(Menu): membuat class anak yang mewarisi semua atribut dan method Menu. Method dengan nama yang sama di anak meng-override milik induk, untuk object anak saja. super() memanggil versi induk: super().__init__(nama, harga) menjalankan constructor induk (tanpa self), dan super().total(jumlah) memakai rumus induk. __init__ anak gak menjalankan milik induk otomatis, jadi tanpa super().__init__ muncul AttributeError. isinstance(teh, Menu) bernilai True untuk turunan juga, beda dengan type(teh) == Menu. Python mencari method menurut MRO: class object dulu, lalu induknya, sampai object.