2.22. Pewarisan

Pewarisan memungkinkan sebuah kelas memperluas kelas lain -- mulai dengan semua metode dan atributnya, kemudian menambahkan atau menimpa apa yang berbeda. Yang asli disebut basis (atau induk); ekstensi disebut subkelas.

A base class Shape pointing down to a subclass Square; Square overrides one method (area) and inherits the rest.

Square menggunakan kembali setiap metode pada Shape dan menimpa yang perlu dispesialisasikan.

2.22.1. Mendefinisikan subkelas

Kelas basis masuk dalam tanda kurung pada baris class:

class Shape:
    def __init__(self, name):
        self.name = name

    def describe(self):
        return self.name + " with area " + str(self.area())

    def area(self):
        return 0

class Square(Shape):
    def __init__(self, side):
        super().__init__("square")
        self.side = side

    def area(self):
        return self.side * self.side

s = Square(4)
print(s.describe())

Output:

square with area 16

Square mewarisi describe dari Shape tanpa perubahan dan menimpa area untuk mengembalikan nilai nyata. describe basis masih berfungsi karena memanggil self.area() -- yang diselesaikan ke override pada waktu berjalan.

2.22.2. super()

super() mengembalikan proxy yang memungkinkan metode memanggil versi kelas-basis dari sebuah metode. Penggunaan paling umum adalah memanggil __init__ basis dari __init__ subkelas sehingga basis dapat menginisialisasi atributnya sendiri:

class Square(Shape):
    def __init__(self, side):
        super().__init__("square")    # run Shape.__init__
        self.side = side

Melupakan super().__init__ adalah sumber bug yang umum: atribut yang seharusnya ditetapkan basis tidak pernah ditetapkan, dan metode yang diwarisi yang bergantung padanya crash kemudian.

2.22.2.1. Kelas basis implisit

Setiap kelas secara implisit mewarisi dari object, akar dari hierarki tipe Python. Metode seperti __repr__, __eq__, dan mekanisme di balik akses atribut semuanya berasal dari sana bahkan ketika sebuah kelas tidak mendeklarasikan basis. Menulis class Foo(object): secara eksplisit dan menulis class Foo: adalah setara dalam Python modern; bentuk terakhir adalah bentuk konvensional.

2.22.3. Kapan pewarisan membantu -- dan kapan tidak

Gunakan pewarisan ketika satu kelas memang jenis yang lebih spesifik dari yang lain, berbagi sebagian besar perilakunya. Tes klasiknya adalah pemeriksaan "is-a": Square adalah Shape.

Ketika dua kelas kebetulan berbagi beberapa pembantu, pewarisan berlebihan. Gunakan komposisi sebagai gantinya: biarkan satu kelas menyimpan instans dari yang lain sebagai atribut dan gunakan melalui atribut tersebut. Bentuknya adalah "has-a", bukan "is-a":

class Logger:
    def log(self, msg):
        print("[log]", msg)

# inheritance: Worker IS-A Logger
class WorkerInherits(Logger):
    def run(self):
        self.log("starting")

# composition: Worker HAS-A Logger
class WorkerComposes:
    def __init__(self):
        self.logger = Logger()

    def run(self):
        self.logger.log("starting")

Keduanya berhasil. Versi komposisi biasanya lebih baik karena:

  • Menjaga antarmuka tetap kecil. WorkerComposes hanya mengekspos run dan logger. WorkerInherits juga mengekspos log -- pemanggil dapat menulis worker.log(...) secara langsung, apakah itu dimaksudkan atau tidak.

  • Memisahkan masa hidup. Logger dapat diganti, dibagikan antar pekerja, atau dikonstruksi secara berbeda per pekerja, semuanya tanpa menyentuh kelas Worker. Subkelas terikat pada basisnya.

  • Menghindari tabrakan nama metode. Dua basis yang keduanya mendefinisikan run bertabrakan ketika sebuah kelas mencoba mewarisi dari keduanya; dua atribut tidak pernah bertabrakan.

Aturan praktis yang berguna:

  • Gunakan pewarisan ketika subkelas benar-benar merupakan jenis dari basis dan setiap metode pada basis masuk akal juga pada subkelas -- Square adalah Shape; MemoryError adalah Exception.

  • Gunakan komposisi untuk segala hal lainnya -- ketika Anda hanya menginginkan perilaku dari kelas lain, bukan identitasnya. Sebagian besar kode nyata menggunakan komposisi jauh lebih banyak daripada menggunakan pewarisan.