Class yang dibangun di atas class lain
Inheritance (pewarisan) memungkinkan kamu mendefinisikan class baru sebagai perluasan dari class yang sudah ada. Class yang sudah ada adalah induk (disebut juga base class atau superclass), dan class baru adalah anak (subclass). Anak mendapat setiap method dan atribut class milik induk tanpa perlu mengulanginya, dan bisa menambahkan miliknya sendiri atau mengganti yang perilakunya harus berbeda.
Kamu menuliskan induknya di dalam tanda kurung setelah nama class:
Output:
Rex is an animal
Woof
Dog tidak punya __init__ maupun describe sendiri. Saat kamu memanggil Dog("Rex"), Python mencari __init__ di Dog, tidak menemukannya, lalu memakai milik Animal. Pencarian yang sama terjadi untuk rex.describe(). Untuk rex.speak(), Python menemukan speak di Dog lebih dulu dan berhenti di situ.
Urutan pencarian itulah seluruh mekanismenya: Python mencari di class milik objek itu sendiri, lalu di induknya, lalu di induk dari induknya, dan memakai kecocokan pertama. Kalau kamu baru mengenal class, halaman class membahas __init__, self, dan method lebih dulu.
Override method
Method di class anak yang namanya sama dengan method di induk akan menggantikannya untuk instance dari anak itu. Ini disebut override. Setiap subclass bisa menjawab pemanggilan yang sama dengan caranya sendiri:
Output:
Rectangle with area 12
Square with area 25
Shape with area 0
summary hanya didefinisikan sekali, di Shape, tapi ia memanggil self.area(). Karena self adalah objek yang sebenarnya, sebuah Rectangle menjalankan Rectangle.area, dan sebuah Square menemukan area di induknya, Rectangle. Kode milik induk memanggil method milik anak tanpa tahu bahwa anak itu ada. Inilah yang membuat inheritance berguna untuk berbagi alur kerja yang sama sambil membiarkan setiap subclass mengisi detailnya.
Memanggil induk dengan super()
Saat anak mendefinisikan __init__ sendiri, __init__ milik induk tidak lagi berjalan dengan sendirinya. Kalau anak tetap butuh persiapan dari induk, panggil dengan super().__init__():
Output:
Ada 1050.0
super() mengembalikan objek yang mencari method mulai dari induk class saat ini. Jadi super().__init__(owner, balance) menjalankan Account.__init__ pada objek yang sama, dan setelahnya self.owner serta self.balance sudah ada.
Kalau baris itu tidak ditulis, objeknya hanya terbangun setengah:
Output:
0.05
AttributeError: 'SavingsAccount' object has no attribute 'owner'
Kamu juga bisa menulis Account.__init__(self, owner), dan kode lama sering melakukannya. super() lebih disarankan karena tidak mengulang nama induk dan bekerja sama dengan benar dalam multiple inheritance, yang dibahas di bawah.
Memperluas method, bukan menggantinya
super() bisa dipakai di method apa pun, bukan hanya __init__. Memanggil versi milik induk di dalam override memungkinkan kamu menambahkan perilaku di sekitarnya alih-alih menulis ulang:
Output:
started
[12:00] started
INFO [12:00] started
Setiap level menambahkan satu bagian dan menyerahkan sisanya ke atas. Kalau format milik induk berubah nanti, kedua anaknya ikut mendapat perubahan itu.
isinstance dan issubclass
Instance dari sebuah subclass juga merupakan instance dari setiap class di atasnya. isinstance(obj, cls) mengecek hubungan itu, dan issubclass(child, parent) mengeceknya di antara dua class:
Utamakan isinstance daripada membandingkan type(obj) secara langsung. Pengecekan seperti type(obj) is Animal menolak setiap subclass, sehingga langsung rusak begitu seseorang menambahkan Dog.
Setiap class yang kamu tulis mewarisi object, bahkan saat kamu tidak menuliskannya. class Animal: sama dengan class Animal(object):. Dari situlah method default seperti __str__, __repr__, dan __eq__ berasal, dan itulah kenapa override __str__ mengubah apa yang ditampilkan print():
Output:
(2, 3)
True
Mewajibkan subclass mengimplementasikan sebuah method
Kadang induk tidak boleh dipakai secara langsung, hanya sebagai template. Modul abc menandai class seperti itu sebagai abstract. Setiap method yang diberi decorator @abstractmethod harus di-override sebelum class itu bisa dibuat instance-nya:
Tiga baris pertama output-nya adalah hasil ekspor:
exporting 2 rows
1,2
3,4
Baris terakhir adalah TypeError yang menyebut Exporter dan method export yang hilang (kalimat persisnya berbeda antar versi Python). Kesalahannya muncul saat objek dibuat, bukan nanti saat export akhirnya dipanggil.
Multiple inheritance dan MRO
Sebuah class bisa menyebutkan lebih dari satu induk. Class itu lalu mewarisi semuanya:
Output:
paddling flapping
swims
['Duck', 'Swimmer', 'Flyer', 'object']
Kedua induk mendefinisikan move, dan Duck mendapat milik Swimmer. Python memutuskannya dengan method resolution order (MRO): satu daftar class, disimpan di Duck.__mro__, yang ditelusuri setiap pencarian atribut dari depan ke belakang. Daftarnya dimulai dengan class itu sendiri, mempertahankan urutan induk seperti yang kamu tulis, menaruh setiap class sebelum induk-induknya sendiri, dan diakhiri dengan object. Tukar urutannya menjadi class Duck(Flyer, Swimmer), maka move mengembalikan "flies".
super() mengikuti daftar yang sama. Di dalam method milik Swimmer, super() berarti "class berikutnya setelah Swimmer di MRO dari class objek itu", yang untuk sebuah Duck adalah Flyer, bukan object. Begitulah class-class yang bekerja sama bisa masing-masing memanggil super() dan membuat setiap class di rantai itu berjalan tepat sekali, bahkan dalam bentuk berlian ketika dua induk punya kakek yang sama.
Dalam praktiknya, multiple inheritance kebanyakan dipakai untuk mixin: class kecil yang menambahkan satu kemampuan, misalnya JsonMixin yang menambahkan method to_json, digabung dengan satu induk utama. Hierarki yang dalam dengan beberapa induk penuh sulit diikuti dan jarang dibutuhkan.
Inheritance atau komposisi?
Inheritance memodelkan hubungan "adalah sebuah" (is a). Sebuah SavingsAccount adalah sebuah Account: kode apa pun yang bekerja dengan Account seharusnya bekerja dengannya tanpa perubahan. Kalau kalimat itu terdengar janggal, mungkin yang kamu inginkan adalah komposisi, yang memodelkan "punya sebuah" (has a): objek menyimpan objek lain sebagai atribut lalu memanggilnya.
Output:
Roadster: 300 hp engine running
class Car(Engine) juga akan berjalan, tapi itu sama saja mengklaim bahwa mobil adalah sejenis mesin, memberi Car setiap method milik mesin, dan membuatnya mustahil menukar dengan mesin lain tanpa class yang berbeda. Dengan komposisi, bagian-bagiannya tetap terpisah, dan mengganti mesin cukup dengan memberikan objek lain.
Beberapa tanda bahwa inheritance adalah alat yang salah:
- Anak menimpa sebagian besar method milik induk, atau menonaktifkan beberapa di antaranya.
- Kamu mewarisi hanya untuk memakai ulang satu atau dua method bantu.
- Hierarkinya tumbuh lebih dari dua atau tiga level, dan kamu harus membaca beberapa file untuk melihat apa yang dilakukan sebuah method.
Kesalahan umum
- Lupa memanggil
super().__init__().__init__milik anak menggantikan milik induk, jadi atribut induk tidak pernah diisi dan nanti kamu mendapatAttributeError. - Mengirim
selfke pemanggilansuper(). Tulissuper().__init__(name), bukansuper().__init__(self, name).super()sudah mengikat method-nya ke objek saat ini. - Signature yang tidak cocok saat override. Kalau
speak(self)milik induk dipanggil oleh kode bersama,speak(self, volume)milik anak yang mewajibkan argumen tambahan akan merusak kode itu. Jaga override tetap kompatibel, atau beri parameter baru nilai default. - Mengecek
type(obj) == SomeClass. Cara ini mengabaikan subclass. Pakaiisinstance. - Memakai inheritance untuk berbagi kode antar class yang tidak berhubungan. Pindahkan kode bersama itu ke sebuah fungsi atau objek bantu.
Pertanyaan yang sering diajukan
Bagaimana cara mewarisi sebuah class di Python?
Taruh class induk di dalam tanda kurung setelah nama class baru: class Dog(Animal):. Dog mendapat setiap method dan atribut class milik Animal, dan bisa menambahkan yang baru atau menimpa (override) yang sudah ada.
Apa yang dilakukan super().__init__()?
Ia memanggil __init__ milik class induk agar induk bisa menyiapkan atributnya sendiri. Saat sebuah subclass mendefinisikan __init__ sendiri, Python tidak memanggil milik induknya secara otomatis, jadi kamu memanggil super().__init__(...) dengan argumen yang dibutuhkan induk, lalu mengisi atribut tambahan milik subclass.
Apa bedanya isinstance dan type di Python?
isinstance(obj, Animal) bernilai True untuk sebuah Animal dan untuk instance dari subclass Animal mana pun. type(obj) is Animal bernilai True hanya kalau objek itu dibuat dari Animal itu sendiri. Utamakan isinstance, karena tetap bekerja saat seseorang menambahkan subclass.
Apakah Python mendukung multiple inheritance?
Ya: class C(A, B): mewarisi keduanya. Saat kedua induk mendefinisikan method yang sama, Python memilih salah satunya dengan mengikuti method resolution order (MRO), yang bisa kamu lihat dengan C.__mro__. Daftarnya dimulai dengan C, lalu induk-induknya dari kiri ke kanan, dan object di akhir.
Kapan sebaiknya memakai komposisi, bukan inheritance?
Pakai inheritance untuk hubungan "adalah sebuah" (is a), ketika subclass bisa dipakai di mana pun induknya diharapkan (sebuah SavingsAccount adalah sebuah Account). Pakai komposisi, yaitu menyimpan objek sebagai atribut, untuk hubungan "punya sebuah" (has a) (sebuah Car punya sebuah Engine). Kalau ragu, komposisi lebih mudah diubah di kemudian hari.