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.
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.
WorkerComposesstelt alleenrunenloggerbeschikbaar.WorkerInheritsstelt ooklogbeschikbaar – aanroepers kunnenworker.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
rundefinië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 –
Squareis eenShape;MemoryErroris eenException.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.