2.22. Dědičnost¶
Dědičnost umožňuje, aby třída rozšířila jinou třídu – začne se všemi jejími metodami a atributy a pak se přidá nebo přepíše to, co je jiné. Originálu se říká základní (nebo rodičovská) třída; rozšíření se říká podtřída.
Square znovu používá každou metodu třídy Shape a přepisuje ty, které potřebuje specializovat.¶
2.22.1. Definice podtřídy¶
Základní třída se uvádí v závorkách na řádku 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())
Výstup:
square with area 16
Square dědí describe ze Shape beze změny a přepisuje area, aby vracela skutečnou hodnotu. Metoda describe ze základní třídy stále funguje, protože volá self.area() – což se za běhu rozliší na přepsanou verzi.
2.22.2. super()¶
super() vrací zástupce (proxy), který umožňuje metodě zavolat verzi metody ze základní třídy. Nejběžnějším použitím je volání základního __init__ z __init__ podtřídy, aby základní třída mohla inicializovat své vlastní atributy:
class Square(Shape):
def __init__(self, side):
super().__init__("square") # run Shape.__init__
self.side = side
Zapomenutí na super().__init__ je častým zdrojem chyb: atributy, které by základní třída nastavila, se nikdy nenastaví a zděděné metody, které se na ně spoléhají, později spadnou.
2.22.2.1. Implicitní základní třída¶
Každá třída implicitně dědí z object, kořene Pythonovské hierarchie typů. Metody jako __repr__, __eq__ a mechanismy za přístupem k atributům pocházejí všechny odtud, i když třída žádnou základní třídu nedeklaruje. Explicitní zápis class Foo(object): a zápis class Foo: jsou v moderním Pythonu ekvivalentní; ta druhá forma je konvenční.
2.22.3. Kdy dědičnost pomáhá – a kdy ne¶
Dědičnost používejte, když je jedna třída skutečně konkrétnějším druhem jiné a sdílí většinu chování. Klasickým testem je kontrola „je“: Square je Shape.
Když dvě třídy jen náhodou sdílejí pár pomocníků, je dědičnost přehnaná. Sáhněte místo toho po kompozici: nechte jednu třídu držet instanci té druhé jako atribut a používejte ji skrz tento atribut. Tvar je „má“, nikoli „je“:
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")
Obojí funguje. Kompoziční verze je obvykle lepší, protože:
Udržuje rozhraní malá.
WorkerComposeszpřístupňuje pouzerunalogger.WorkerInheritszpřístupňuje navíc ilog– volající mohou napsatworker.log(...)přímo, ať to bylo zamýšleno, nebo ne.Odděluje životní cykly. Logger lze vyměnit, sdílet mezi pracovníky nebo zkonstruovat pro každého pracovníka jinak, a to vše bez zásahu do třídy
Worker. Podtřída je k základní třídě přišroubovaná.Předchází kolizím názvů metod. Dvě základní třídy, které obě definují
run, se střetnou, když se z nich třída pokusí dědit najednou; dva atributy se nikdy nestřetnou.
Praktické orientační pravidlo:
Dědičnost používejte, když podtřída opravdu je druhem základní třídy a každá metoda základní třídy na ní také dává smysl –
SquarejeShape;MemoryErrorjeException.Kompozici používejte pro vše ostatní – když chcete jen chování jiné třídy, nikoli její identitu. Většina reálného kódu používá kompozici mnohem častěji než dědičnost.