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.

Základní třída Shape ukazující dolů na podtřídu Square; Square přepisuje jednu metodu (area) a zbytek dědí.

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á. WorkerComposes zpřístupňuje pouze run a logger. WorkerInherits zpřístupňuje navíc i log – volající mohou napsat worker.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 – Square je Shape; MemoryError je Exception.

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