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.