2.22. Moștenire

Moștenirea permite unei clase să extindă o altă clasă – să pornească de la toate metodele și atributele acesteia, apoi să adauge sau să suprascrie ceea ce diferă. Originalul se numește clasa de bază (sau părinte); extensia se numește subclasă.

O clasă de bază Shape care indică în jos spre o subclasă Square; Square suprascrie o metodă (area) și moștenește restul.

Square reutilizează fiecare metodă din Shape și le suprascrie pe cele pe care trebuie să le specializeze.

2.22.1. Definirea unei subclase

Clasa de bază se pune în parantezele de pe linia 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())

Ieșire:

square with area 16

Square moștenește describe din Shape neschimbată și suprascrie area pentru a returna o valoare reală. Metoda describe a clasei de bază funcționează în continuare deoarece apelează self.area() – care se rezolvă la suprascriere în timpul execuției.

2.22.2. super()

super() returnează un proxy care permite unei metode să apeleze versiunea clasei de bază a unei metode. Cea mai frecventă utilizare este apelarea metodei __init__ a clasei de bază din __init__ al unei subclase, astfel încât clasa de bază să-și poată inițializa propriile atribute:

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

Uitarea apelului super().__init__ este o sursă frecventă de erori: atributele pe care clasa de bază le-ar fi setat nu sunt niciodată setate, iar metodele moștenite care se bazează pe ele eșuează mai târziu.

2.22.2.1. Clasa de bază implicită

Fiecare clasă moștenește implicit de la object, rădăcina ierarhiei de tipuri din Python. Metode precum __repr__, __eq__ și mecanismul din spatele accesului la atribute provin de acolo, chiar și atunci când o clasă nu declară nicio clasă de bază. Scrierea explicită class Foo(object): și scrierea class Foo: sunt echivalente în Python modern; cea din urmă este forma convențională.

2.22.3. Când ajută moștenirea – și când nu

Folosește moștenirea atunci când o clasă este cu adevărat un tip mai specific al alteia, partajând majoritatea comportamentului. Testul clasic este verificarea „este-un/o”: un Square este un Shape.

Când două clase pur și simplu se întâmplă să partajeze câteva funcții ajutătoare, moștenirea este exagerată. Recurge în schimb la compoziție: fă o clasă să dețină o instanță a celeilalte drept atribut și folosește-o prin acel atribut. Forma este „are-un/o”, nu „este-un/o”:

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")

Ambele funcționează. Versiunea cu compoziție este de obicei mai bună deoarece:

  • Menține interfețele mici. WorkerComposes expune doar run și logger. WorkerInherits expune și log – apelanții pot scrie worker.log(...) direct, indiferent dacă acest lucru a fost intenționat sau nu.

  • Decuplează duratele de viață. Logger-ul poate fi schimbat, partajat între workeri sau construit diferit pentru fiecare worker, totul fără a atinge clasa Worker. O subclasă este sudată de clasa sa de bază.

  • Evită coliziunile de nume de metode. Două clase de bază care definesc ambele run intră în conflict atunci când o clasă încearcă să moștenească de la amândouă; două atribute nu se ciocnesc niciodată.

O regulă practică:

  • Folosește moștenirea atunci când subclasa chiar este un tip al clasei de bază și fiecare metodă a clasei de bază are sens și pentru ea – Square este un Shape; MemoryError este o Exception.

  • Folosește compoziția pentru orice altceva – atunci când vrei doar comportamentul unei alte clase, nu identitatea ei. Cel mai mult cod real folosește compoziția mult mai mult decât moștenirea.