2.22. Overerving

Overerving laat een klasse een andere klasse uitbreiden – beginnen met al zijn methoden en attributen, en vervolgens toevoegen of overschrijven wat anders is. Het origineel wordt de basis (of ouder) genoemd; de uitbreiding wordt de subklasse genoemd.

Een basisklasse Shape die naar beneden wijst naar een subklasse Square; Square overschrijft één methode (area) en erft de rest.

Square hergebruikt elke methode op Shape en overschrijft de methoden die het moet specialiseren.

2.22.1. Een subklasse definiëren

De basisklasse gaat tussen de haakjes op de class-regel:

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())

Uitvoer:

square with area 16

Square erft describe ongewijzigd van Shape en overschrijft area om een echte waarde terug te geven. De describe van de basis werkt nog steeds omdat hij self.area() aanroept – die op runtime naar de override wordt opgelost.

2.22.2. super()

super() geeft een proxy terug die een methode de basisklasse-versie van een methode laat aanroepen. Het meest voorkomende gebruik is het aanroepen van de basis-__init__ vanuit een subklasse-__init__ zodat de basis zijn eigen attributen kan initialiseren:

class Square(Shape):
    def __init__(self, side):
        super().__init__("square")    # run Shape.__init__
        self.side = side

super().__init__ vergeten is een veelvoorkomende bron van bugs: attributen die de basis zou hebben ingesteld, worden nooit ingesteld, en de overgeërfde methoden die erop steunen, crashen later.

2.22.2.1. De impliciete basisklasse

Elke klasse erft impliciet van object, de wortel van Pythons typehiërarchie. Methoden zoals __repr__, __eq__, en de machinerie achter attribuuttoegang komen allemaal daarvandaan, zelfs wanneer een klasse geen basis declareert. class Foo(object): expliciet schrijven en class Foo: schrijven zijn equivalent in modern Python; het laatste is de conventionele vorm.

2.22.3. Wanneer overerving helpt – en wanneer niet

Gebruik overerving wanneer een klasse oprecht een specifiekere soort van een andere is, en het meeste gedrag deelt. De klassieke test is de “is-een”-controle: een Square is een Shape.

Wanneer twee klassen toevallig slechts een paar helpers delen, is overerving overdreven. Grijp in plaats daarvan naar compositie: laat de ene klasse een instance van de andere als attribuut bevatten en gebruik die via dat attribuut. De vorm is “heeft-een”, niet “is-een”:

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")

Beide werken. De composerende versie is meestal beter omdat ze:

  • Interfaces klein houdt. WorkerComposes stelt alleen run en logger beschikbaar. WorkerInherits stelt ook log beschikbaar – aanroepers kunnen worker.log(...) rechtstreeks schrijven, of dat nu bedoeld was of niet.

  • Levensduren ontkoppelt. De logger kan worden verwisseld, gedeeld tussen workers, of per worker anders worden geconstrueerd, allemaal zonder de Worker-klasse aan te raken. Een subklasse zit vastgeschroefd aan zijn basis.

  • Botsingen van methodenamen vermijdt. Twee bases die beide run definiëren botsen wanneer een klasse van beide probeert te erven; twee attributen botsen nooit.

Een praktische vuistregel:

  • Gebruik overerving wanneer de subklasse echt een soort van de basis is en elke methode op de basis er ook op zinvol is – Square is een Shape; MemoryError is een Exception.

  • Gebruik compositie voor al het andere – wanneer je alleen het gedrag van een andere klasse wilt, niet zijn identiteit. De meeste echte code gebruikt compositie veel vaker dan overerving.