Lewati ke konten utama

Pengantar Multiple Inheritance & super()

Panduan pengantar untuk Pythonista tentang multiple inheritance, fungsi super(), dan cara menavigasi diamond problem.
· 8 mnt Baca

Jelajahi bersama AI

Gambaran singkat tentang inheritance

Seiring bertambah besarnya proyek dan paket Python Anda, pada akhirnya Anda akan ingin memanfaatkan kelas dan menerapkan prinsip DRY (don't-repeat-yourself) saat melakukannya. Inheritance kelas adalah cara yang sangat baik untuk membuat kelas berdasarkan kelas lain agar tetap DRY. Tulisan ini membahas konsep inheritance yang lebih lanjut, dan inheritance dasar tidak dibahas secara mendalam. Kita akan meninjau pengantar singkat, namun ada banyak pengantar yang jauh lebih lengkap di luar sana. Berikut beberapa sumber untuk memulai: kursus Object-Oriented Programming in Python & Python Object-Oriented Programming (OOP): Tutorial.

Lalu apa itu inheritance kelas? Mirip dengan genetika, sebuah child class dapat "mewarisi" atribut dan metode dari parent. Mari langsung ke contoh kode. Pada blok kode di bawah ini, kita mendemonstrasikan inheritance dengan kelas Child yang mewarisi dari kelas Parent.

Input

class Parent:
    def __init__(self):
        self.parent_attribute = 'I am a parent'

    def parent_method(self):
        print('Back in my day...')


# Create a child class that inherits from Parent
class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
        self.child_attribute = 'I am a child'


# Create instance of child
child = Child()

# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()

Output

I am a child
I am a parent
Back in my day...

Kita melihat bahwa kelas Child "mewarisi" atribut dan metode dari kelas Parent. Tanpa usaha apa pun dari pihak kita, Parent.parent_method menjadi bagian dari kelas Child. Untuk mendapatkan manfaat dari metode Parent.__init__() kita perlu memanggilnya secara eksplisit dan meneruskan self. Ini karena ketika kita menambahkan metode __init__ ke Child, kita menimpa __init__ yang diwarisi.

Dengan gambaran singkat yang sangat tidak komprehensif itu, mari masuk ke inti bahasan.

Pengantar super

Pada kasus paling sederhana, fungsi super dapat digunakan untuk menggantikan pemanggilan eksplisit Parent.__init__(self). Contoh pengantar pada bagian pertama dapat ditulis ulang dengan super seperti di bawah ini. Perhatikan bahwa blok kode di bawah ditulis dalam Python 3; versi sebelumnya menggunakan sintaks yang sedikit berbeda. Selain itu, output dihilangkan karena identik dengan blok kode pertama.

class Parent:
    def __init__(self):
        self.parent_attribute = 'I am a parent'

    def parent_method(self):
        print('Back in my day...')


# Create a child class that inherits from Parent
class Child(Parent):
    def __init__(self):
        super().__init__()
        self.child_attribute = 'I am a parent'


# Create instance of child
child = Child()

# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()

Sejujurnya, super dalam kasus ini memberi sedikit, kalaupun ada, keuntungan. Tergantung pada nama kelas induk, kita mungkin menghemat beberapa ketukan tombol, dan kita tidak perlu meneruskan self ke pemanggilan __init__. Di bawah ini beberapa pro dan kontra penggunaan super dalam kasus single inheritance.

Kontra

Dapat dikatakan bahwa menggunakan super di sini membuat kode kurang eksplisit. Membuat kode kurang eksplisit melanggar The Zen of Python, yang menyatakan, "Explicit is better than implicit."

Pro

Ada argumen maintainability yang bisa dibuat untuk super bahkan dalam single inheritance. Jika karena alasan apa pun child class Anda mengubah pola inheritance-nya (misalnya, parent class berubah atau beralih ke multiple inheritance) maka tidak perlu mencari dan mengganti semua referensi yang tersisa ke ParentClass.method_name(); penggunaan super akan memungkinkan semua perubahan mengikuti perubahan pada pernyataan class.

super dan multiple inheritance

Sebelum kita masuk ke multiple inheritance dan super... Peringatan, ini bisa menjadi cukup aneh dan rumit.

Pertama-tama, apa itu multiple inheritance? Sejauh ini contoh kode membahas satu child class yang mewarisi dari satu parent class. Dalam multiple inheritance, ada lebih dari satu parent class. Sebuah child class dapat mewarisi dari 2, 3, 10, dan seterusnya parent class.

Di sinilah manfaat super menjadi lebih jelas. Selain menghemat ketukan tombol untuk mereferensikan berbagai nama parent class, ada manfaat bernuansa dalam menggunakan super dengan pola multiple inheritance. Singkatnya, jika Anda akan menggunakan multiple inheritance, gunakan super.

Multiple inheritance tanpa super

Mari lihat contoh multiple inheritance yang menghindari modifikasi metode parent mana pun dan pada gilirannya menghindari super.

Input

class B:
    def b(self):
        print('b')


class C:
    def c(self):
        print('c')


class D(B, C):
    def d(self):
        print('d')


d = D()
d.b()
d.c()
d.d()

Output

b
c
d

Multiple-resolution order

Output ini tidak terlalu mengejutkan mengingat konsep multiple inheritance. D mewarisi metode x dan z dari parent class-nya, dan semuanya baik-baik saja... untuk saat ini.

Lalu bagaimana jika B dan C sama-sama memiliki metode dengan nama yang sama? Di sinilah konsep yang disebut 'multiple-resolution order' berperan atau disingkat MRO. MRO dari sebuah child class yang menentukan di mana Python akan mencari suatu metode, dan metode mana yang akan dipanggil ketika terjadi konflik.

Mari lihat contohnya.

Input

class B:
    def x(self):
        print('x: B')


class C:
    def x(self):
        print('x: C')


class D(B, C):
    pass


d = D()
d.x()
print(D.mro())

Output

x: B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]

Saat kita memanggil metode x yang diwarisi, kita hanya melihat output yang diwarisi dari B. Kita dapat melihat MRO dari kelas D kita dengan memanggil metode kelas mro. Dari output D.mro() kita mengetahui hal berikut: program kita akan mencoba memanggil metode D secara default, lalu beralih ke B, kemudian C, dan terakhir object. Jika tidak ditemukan di tempat-tempat tersebut, maka kita akan mendapatkan error bahwa D tidak memiliki metode yang kita minta.

Perlu dicatat bahwa secara default, setiap kelas mewarisi dari object, dan itu berada di bagian akhir setiap MRO.

Multiple inheritance, super, dan diamond problem

Di bawah ini contoh penggunaan super untuk menangani MRO dari init dengan cara yang bermanfaat. Pada contoh ini, kita membuat serangkaian kelas pemrosesan teks dan menggabungkan fungsinya dalam kelas lain dengan multiple inheritance. Kita akan membuat 4 kelas, dan struktur inheritance akan mengikuti struktur pada diagram di bawah.

Catatan: Struktur ini untuk tujuan ilustrasi, dan, jika tidak ada kendala, ada cara yang lebih baik untuk mengimplementasikan logika ini.

diamond problem

Ini sebenarnya contoh 'diamond problem' pada multiple inheritance. Namanya tentu berdasarkan bentuk desainnya, dan fakta bahwa ini adalah masalah yang cukup membingungkan.

Di bawah ini desainnya dituliskan dengan penggunaan super.

Input

class Tokenizer:
    """Tokenize text"""
    def __init__(self, text):
        print('Start Tokenizer.__init__()')
        self.tokens = text.split()
        print('End Tokenizer.__init__()')


class WordCounter(Tokenizer):
    """Count words in text"""
    def __init__(self, text):
        print('Start WordCounter.__init__()')
        super().__init__(text)
        self.word_count = len(self.tokens)
        print('End WordCounter.__init__()')


class Vocabulary(Tokenizer):
    """Find unique words in text"""
    def __init__(self, text):
        print('Start init Vocabulary.__init__()')
        super().__init__(text)
        self.vocab = set(self.tokens)
        print('End init Vocabulary.__init__()')


class TextDescriber(WordCounter, Vocabulary):
    """Describe text with multiple metrics"""
    def __init__(self, text):
        print('Start init TextDescriber.__init__()')
        super().__init__(text)
        print('End init TextDescriber.__init__()')


td = TextDescriber('row row row your boat')
print('--------')
print(td.tokens)
print(td.vocab)
print(td.word_count)

Output

Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
--------
['row', 'row', 'row', 'your', 'boat']
{'boat', 'your', 'row'}
5

Pertama, kita melihat kelas TextDescriber mewarisi semua atribut dari pohon keluarga kelas. Berkat multiple inheritance kita dapat "menggabungkan" fungsionalitas lebih dari satu kelas.

Sekarang mari bahas keluaran print yang berasal dari metode init setiap kelas:

Setiap metode __init__ dipanggil satu kali dan hanya satu kali.

Kelas TextDescriber mewarisi dari 2 kelas yang mewarisi dari Tokenizer. Mengapa Tokenizer.__init__ tidak dipanggil dua kali?

Jika kita mengganti semua pemanggilan super dengan cara lama, kita akan berakhir dengan 2 pemanggilan Tokenizer.__init__. Pemanggilan ke super lebih "memikirkan" pola kita dan melewatkan perjalanan ekstra ke A.

Setiap metode __init__ dimulai sebelum salah satu yang lain selesai.

Urutan mulai dan selesai dari setiap __init__ patut diperhatikan jika Anda mencoba menetapkan atribut yang memiliki konflik penamaan dengan parent class lain. Atribut tersebut akan ditimpa, dan ini bisa menjadi sangat membingungkan.

Dalam kasus kita, kita menghindari konflik penamaan dengan atribut yang diwarisi, sehingga semuanya bekerja sesuai harapan.

Perlu ditegaskan, diamond problem bisa menjadi rumit dengan cepat dan menyebabkan hasil yang tidak terduga. Dalam sebagian besar kasus pemrograman, sebaiknya hindari desain yang rumit.

Apa yang kita pelajari

  • Kita mempelajari fungsi super dan bagaimana fungsi ini dapat digunakan untuk menggantikan ParentName.method dalam single inheritance. Ini bisa menjadi praktik yang lebih mudah dirawat.
  • Kita mempelajari multiple inheritance dan bagaimana kita dapat mewariskan fungsionalitas dari beberapa parent class ke satu child class.
  • Kita mempelajari multiple-resolution order dan bagaimana hal itu menentukan apa yang terjadi dalam multiple inheritance ketika ada konflik penamaan antar metode parent.
  • Kita mempelajari diamond problem dan melihat contoh bagaimana penggunaan super menavigasi diamond tersebut.

Topik

Terkait

blogs

Spaghetti Plot dan Jalur Badai

blogs

40 Pertanyaan Wawancara DBMS Teratas di 2026

blogs

Tutorial Korelasi di R