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ă.

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

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.