Multiple inheritance dan MRO

Multiple inheritance, Method Resolution Order, mixin, dan penanganan kasus diamond.

Daftar isi

Python mengizinkan sebuah kelas mewarisi lebih dari satu kelas induk sekaligus, dan mekanisme ini disebut pewarisan ganda (multiple inheritance). Kemampuan tersebut menambah kekuatan sekaligus pertanyaan baru, yaitu urutan kelas mana yang ditelusuri Python ketika sebuah metode dipanggil. Urutan itu dinamakan Method Resolution Order, biasa disingkat MRO. Topik ini membahas pewarisan dari dua kelas, cara kerja MRO, kasus diamond, serta pola mixin.

Pewarisan dari dua kelas#

Subclass dengan dua induk menuliskan kedua induknya di dalam tanda kurung, dipisahkan tanda koma. Python mencari metode dengan menelusuri induk dari kiri ke kanan sesuai urutan penulisannya. Atribut __mro__ pada kelas memuat urutan pencarian itu secara lengkap, sehingga perilaku pencarian dapat diperiksa secara langsung alih-alih ditebak.

python Contoh: dua kelas induk dan MRO-nya
class Dokumen:
    def simpan(self):
        print("menyimpan dokumen")


class Tampilan:
    def render(self):
        print("merender tampilan")


class DokumenWeb(Dokumen, Tampilan):
    pass

d = DokumenWeb()
d.simpan()
d.render()
print([k.__name__ for k in DokumenWeb.__mro__])
text Keluaran
menyimpan dokumen
merender tampilan
['DokumenWeb', 'Dokumen', 'Tampilan', 'object']

Instans DokumenWeb dapat memanggil kedua metode karena keduanya ditemukan pada induknya. Daftar MRO menunjukkan urutan telusurnya, yaitu kelas itu sendiri, lalu Dokumen, Tampilan, dan terakhir object yang menjadi induk semua kelas. Pencarian berhenti pada kelas pertama yang memiliki metode tersebut, sehingga apabila kedua induk sama-sama mendefinisikan satu nama, induk yang paling kiri menang. Urutan penulisan induk karenanya bukan perkara kosmetik, melainkan bagian dari desain kelasnya.

Method Resolution Order#

Pewarisan ganda memunculkan kasus diamond, yaitu dua kelas induk yang sama-sama mewarisi satu kelas dasar. Apabila pencarian dilakukan secara sederhana dari kiri ke kanan berulang kali, metode kelas dasar dapat dieksekusi dua kali. Untuk itu, Python menghitung MRO dengan algoritma C3 linearization, yang menjamin setiap kelas muncul tepat sekali dan berada sebelum induknya. Diagram berikut menampilkan bentuk diamond beserta urutan MRO hasilnya.

DasarKiriKananBawahmewarisiMRO Bawah:Bawah → Kiri → Kanan → Dasar → object
Kasus diamond dan urutan MRO kelas Bawah

Contoh berikut membangun diamond pada diagram tersebut. Setiap kelas menyerahkan sisa pekerjaan kepada super(), sehingga pemanggilan mengalir mengikuti MRO, bukan mengikuti induk langsung masing-masing kelas.

python Contoh: diamond dengan super() kooperatif
class Dasar:
    def sapa(self):
        print("sapa dari Dasar")


class Kiri(Dasar):
    def sapa(self):
        print("sapa dari Kiri")
        super().sapa()


class Kanan(Dasar):
    def sapa(self):
        print("sapa dari Kanan")
        super().sapa()


class Bawah(Kiri, Kanan):
    def sapa(self):
        print("sapa dari Bawah")
        super().sapa()

b = Bawah()
b.sapa()
print([k.__name__ for k in Bawah.__mro__])
text Keluaran
sapa dari Bawah
sapa dari Kiri
sapa dari Kanan
sapa dari Dasar
['Bawah', 'Kiri', 'Kanan', 'Dasar', 'object']

Meskipun Kiri dan Kanan sama-sama mewarisi Dasar, keluaran sapa dari Dasar hanya muncul sekali. Pada Kiri, pemanggilan super().sapa() tidak menuju Dasar, melainkan menuju kelas berikutnya pada MRO instans, yaitu Kanan. Perilaku inilah yang dimaksud kooperatif, karena setiap kelas tidak menganggap dirinya pusat rantai, melainkan menyerahkan lanjutan kepada MRO kelas instans yang bersangkutan.

Mixin: kemampuan kecil yang diserap#

Pola mixin memanfaatkan pewarisan ganda untuk menyuntikkan satu kemampuan ke beberapa kelas tanpa membentuk hierarki yang dalam. Mixin adalah kelas kecil yang mendefinisikan satu perilaku dan tidak dimaksudkan untuk berdiri sendiri. Kelas penerima menuliskan mixin di awal daftar induknya, dan MRO memastikan metode mixin ditemukan terlebih dahulu. Berkas berikut menambahkan kemampuan ekspor JSON kepada kelas Sensor.

python mixin-laporan.py
import json


class LaporanJSON:
    # mixin: satu kemampuan, tidak berdiri sendiri
    def ke_json(self):
        return json.dumps(self.__dict__, sort_keys=True)


class Sensor(LaporanJSON):
    def __init__(self, lokasi, nilai):
        self.lokasi = lokasi
        self.nilai = nilai

s = Sensor("gudang", 28.5)
print(s.ke_json())
text Keluaran
{"lokasi": "gudang", "nilai": 28.5}

Simpan berkas di atas di direktori kerja, lalu jalankan dengan perintah berikut.

bash
# menjalankan program mixin laporan JSON
python3 mixin-laporan.py
text Keluaran
{"lokasi": "gudang", "nilai": 28.5}

Keluaran berupa satu baris JSON dengan kunci terurut, hasil dari argumen sort_keys=True pada json.dumps(). Mixin semacam ini menempatkan perilaku di satu tempat, sementara kelas penerima tetap fokus pada datanya. Pendekatan yang sama digunakan banyak pustaka untuk kemampuan lintas kelas, misalnya serialisasi, pencatatan, dan pembandingan, tanpa menuntut hierarki kelas yang dalam.

Metode ke_json() tidak bergantung pada Sensor, karena hanya membaca self.__dict__ milik instansnya. Karena itu, mixin yang sama dapat diserap oleh kelas lain tanpa diubah. Pola ini lazim pada pustaka Python, dan penamaan mixin biasanya menjelaskan kemampuannya secara langsung, seperti LaporanJSON. Apabila sebuah mixin memanggil super(), mixin tersebut harus dirancang menganggap kelas berikutnya pada MRO selalu ada, sehingga urutan penulisan induk menjadi bagian dari desainnya.

Kesalahan umum#

Kesalahan pertama adalah menyusun induk dengan urutan yang bertentangan dengan hierarkinya, sehingga Python menolak mendefinisikan kelas tersebut.

python Contoh: MRO yang tidak dapat diselesaikan
class Induk:
    pass


class Anak(Induk):
    pass

try:
    class Rusak(Induk, Anak):
        pass
except TypeError as e:
    print(type(e).__name__, str(e))
text Keluaran
TypeError Cannot create a consistent method resolution
order (MRO) for bases Induk, Anak

Urutan (Induk, Anak) memaksa Induk dicari lebih dulu, padahal Anak adalah subclass dari Induk dan harus berada sebelum induknya. Algoritma C3 tidak dapat memenuhi keduanya sekaligus, sehingga pesan TypeError muncul tepat saat kelas didefinisikan. Perbaikannya adalah menukar urutan induk menjadi (Anak, Induk), atau meninjau ulang desain hierarkinya.

Kesalahan kedua adalah mencampur pemanggilan super() dengan pemanggilan induk secara eksplisit pada kasus diamond. Kombinasi tersebut membuat kelas dasar dieksekusi lebih dari satu kali, sebagaimana keluaran berikut yang menampilkan sapa dari Dasar dua kali.

python Contoh: kelas dasar dipanggil dua kali
class Dasar:
    def sapa(self):
        print("sapa dari Dasar")


class KiriLurus(Dasar):
    def sapa(self):
        print("sapa dari KiriLurus")
        Dasar.sapa(self)


class KananLurus(Dasar):
    def sapa(self):
        print("sapa dari KananLurus")
        Dasar.sapa(self)


class BawahLurus(KiriLurus, KananLurus):
    def sapa(self):
        KiriLurus.sapa(self)
        KananLurus.sapa(self)

b = BawahLurus()
b.sapa()
text Keluaran
sapa dari KiriLurus
sapa dari Dasar
sapa dari KananLurus
sapa dari Dasar

Setiap kelas turunan memanggil Dasar.sapa(self) secara langsung, sehingga kelas dasar berjalan sekali untuk setiap cabang tanpa koordinasi antarcabang. Pada inisialisasi, efeknya berupa pekerjaan ganda yang mahal atau bahkan salah, misalnya sambungan dibuka dua kali. Perbaikannya adalah memakai super() di seluruh rantai, sehingga setiap kelas menyerahkan lanjutan kepada MRO dan kelas dasar dijalankan tepat satu kali. Kesalahan ketiga adalah memperlakukan pewarisan ganda sebagai tempat menggabungkan kelas-kelas besar, padahal pola yang terbukti mudah dirawat adalah kombinasi satu kelas dasar dengan beberapa mixin kecil.

Ringkasan#

  • Subclass dapat menuliskan lebih dari satu induk, dan pencarian metode mengikuti urutan induk dari kiri ke kanan.
  • Atribut __mro__ memuat urutan pencarian lengkap yang dihitung dengan algoritma C3 linearization.
  • Pada kasus diamond, super() mengalirkan pemanggilan mengikuti MRO instans, sehingga kelas dasar dieksekusi tepat satu kali.
  • Mixin menyuntikkan satu kemampuan kecil ke kelas mana pun, dengan mixin dituliskan di awal daftar induk.

Referensi#