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:

python
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:

text
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
['D', 'B', 'C', 'A', 'object']

Mari kita baca hasilnya:

  1. Baris pertama adalah tuple mentah __mro__; baris kedua hanya versi yang lebih mudah dibaca (nama class saja).
  2. Urutannya: D → B → C → A → object. Pencarian method untuk instance D selalu dimulai dari class D sendiri, lalu mengikuti urutan ini sampai method ditemukan.
  3. Karena itulah d.sapa() menampilkan versi C: D tidak punya sapa, B juga tidak, dan C ditemukan lebih dulu sebelum A.
  4. 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:

  1. Anak selalu mendahului induk. D selalu di depan B, C, dan A.
  2. Urutan penulisan induk dihormati. D(B, C) menempatkan B di depan C; D(C, B) akan menghasilkan urutan kebalikannya.
  3. Setiap class muncul tepat sekali. A adalah leluhur dari tiga jalur sekaligus, tetapi dalam MRO A hanya hadir satu kali — setelah semua keturunannya.

Ketiga janji itu kadang tidak bisa dipenuhi bersamaan. Saat itu terjadi, Python menolak membuat class:

text
TypeError: Cannot create a consistent method resolution
order (MRO) for bases A, B

Pesan 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:

python
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:

text
menyimpan ke disk lokal
menyimpan ke cloud

BackupLokalDulu 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:

python
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:

text
D.__init__ dijalankan
B.__init__ dijalankan
C.__init__ dijalankan
A.__init__ dijalankan

Mari kita lacak urutannya bersama MRO D → B → C → A:

  1. D() menjalankan __init__ milik D lebih dulu — sesuai urutan MRO.
  2. super().__init__() di dalam D melanjutkan ke B. Wajar.
  3. super().__init__() di dalam B ternyata melanjutkan ke C, bukan A — karena dalam MRO milik object d, class setelah B adalah C.
  4. super().__init__() di dalam C melanjutkan ke A, 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.