Chapter Multiple Inheritance menutup dengan teka-teki: D(B, C) mendapatkan sapa() dari C, padahal B ditulis lebih dulu. Keputusan itu tidak diambil secara misterius — Python membangun daftar pencarian yang pasti untuk setiap class, dan daftar itu bisa kita baca kapan saja. Daftarnya bernama Method Resolution Order (MRO): urutan class yang dilalui interpreter setiap kali mencari method atau attribute. Chapter ini membaca MRO langsung dari dalam class, memahami aturan yang membentuknya, lalu melihat bagaimana MRO menjelaskan dua perilaku yang mengejutkan — hasil pencarian yang berubah mengikuti urutan penulisan induk, dan super() yang tidak menuju ke induk.
Membaca mro #
Setiap class membawa tuple __mro__ berisi dirinya sendiri beserta seluruh leluhurnya, tersusun sesuai urutan pencarian. Kembali ke belah ketupat dari chapter sebelumnya:
class A:
def sapa(self):
return "sapa dari A"
class B(A):
pass
class C(A):
def sapa(self):
return "sapa dari C"
class D(B, C):
pass
print(D.__mro__)
print([cls.__name__ for cls in D.__mro__])Output yang Dihasilkan:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
['D', 'B', 'C', 'A', 'object']Mari kita baca hasilnya:
- Baris pertama adalah tuple mentah
__mro__; baris kedua hanya versi yang lebih mudah dibaca (nama class saja). - Urutannya:
D → B → C → A → object. Pencarian method untuk instanceDselalu dimulai dari classDsendiri, lalu mengikuti urutan ini sampai method ditemukan. - Karena itulah
d.sapa()menampilkan versiC:Dtidak punyasapa,Bjuga tidak, danCditemukan lebih dulu sebelumA. - Elemen terakhir selalu
object— class dasar segala class, persis seperti dibahas pada Chapter Memahami Class dan Object.
Algoritma C3 dalam Kata Sederhana #
Urutan pada __mro__ dihitung oleh algoritma C3 linearization — Python memakainya sejak versi 2.3. Rumus matematisnya tidak perlu kita hafal; tiga janjinya cukup:
- Anak selalu mendahului induk.
Dselalu di depanB,C, danA. - Urutan penulisan induk dihormati.
D(B, C)menempatkanBdi depanC;D(C, B)akan menghasilkan urutan kebalikannya. - Setiap class muncul tepat sekali.
Aadalah leluhur dari tiga jalur sekaligus, tetapi dalam MROAhanya hadir satu kali — setelah semua keturunannya.
Ketiga janji itu kadang tidak bisa dipenuhi bersamaan. Saat itu terjadi, Python menolak membuat class:
TypeError: Cannot create a consistent method resolution
order (MRO) for bases A, BPesan galat di atas muncul, misalnya, jika kita menulis class C(A, B) padahal B sudah mewarisi A — urutan yang diminta bertentangan dengan janji “anak mendahului induk”. (Pesan tersebut dipecah dua baris oleh Python sendiri, bukan oleh tampilan terminal.) Python memilih berhenti daripada menebak-nebak.
Urutan Induk Memengaruhi Hasil Pencarian #
Janji kedua algoritma C3 paling sering terasa dampaknya. Dua class berikut hanya berbeda urutan penulisan induk — dan keduanya berperilaku berbeda:
class PenyimpananLokal:
def simpan(self):
return "menyimpan ke disk lokal"
class PenyimpananCloud:
def simpan(self):
return "menyimpan ke cloud"
class BackupLokalDulu(PenyimpananLokal, PenyimpananCloud):
pass
class BackupCloudDulu(PenyimpananCloud, PenyimpananLokal):
pass
backup1 = BackupLokalDulu()
backup2 = BackupCloudDulu()
print(backup1.simpan())
print(backup2.simpan())Output yang Dihasilkan:
menyimpan ke disk lokal
menyimpan ke cloudBackupLokalDulu mencari simpan() di PenyimpananLokal lebih dulu, BackupCloudDulu di PenyimpananCloud lebih dulu — dan masing-masing menemukan method yang berbeda. Ketika kedua induk menyediakan method bernama sama, urutan di dalam kurung menjadi keputusan desain yang nyata. Framework besar memakai kebiasaan penamaan untuk memperjelas hal ini: class seperti LogMixin sengaja diletakkan paling kiri karena method-nya harus “menang” atas induk utama.
super() Mengikuti MRO, Bukan Induk Langsung #
Penemuan paling mengejutkan dari MRO menyangkut super(). Intuisi banyak orang: super() di dalam class B berarti “panggil versi di induk B”. Kenyataannya, super() berarti “lanjutkan ke class berikutnya dalam MRO object yang menjalankan method ini”. Perhatikan:
class A:
def __init__(self):
print("A.__init__ dijalankan")
class B(A):
def __init__(self):
print("B.__init__ dijalankan")
super().__init__()
class C(A):
def __init__(self):
print("C.__init__ dijalankan")
super().__init__()
class D(B, C):
def __init__(self):
print("D.__init__ dijalankan")
super().__init__()
d = D()Output yang Dihasilkan:
D.__init__ dijalankan
B.__init__ dijalankan
C.__init__ dijalankan
A.__init__ dijalankanMari kita lacak urutannya bersama MRO D → B → C → A:
D()menjalankan__init__milikDlebih dulu — sesuai urutan MRO.super().__init__()di dalamDmelanjutkan keB. Wajar.super().__init__()di dalamBternyata melanjutkan keC, bukanA— karena dalam MRO milik objectd, class setelahBadalahC.super().__init__()di dalamCmelanjutkan keA, dan rantai selesai.A.__init__dijalankan tepat satu kali.
Jika super() dimaknai “panggil induk”, A.__init__ akan dijalankan dua kali (sekali dari B, sekali dari C) — klasiknya diamond problem. MRO menuntaskan masalah ini: setiap class dalam rantai dipanggil maksimal satu kali, dalam urutan yang bisa diprediksi. Cara memakai super() secara sadar atas mekanisme ini menjadi pokok bahasan chapter berikutnya.