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ă.
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.
WorkerComposesexpune doarrunșilogger.WorkerInheritsexpune șilog– apelanții pot scrieworker.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
runintră î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 –
Squareeste unShape;MemoryErroreste oException.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.