super en meervoudige overerving
Voordat we in meervoudige overerving en super duiken… Waarschuwing: dit kan best vreemd en ingewikkeld worden.
Om te beginnen: wat is meervoudige overerving? Tot nu toe ging de voorbeeldcode over één child-klasse die erft van één parent-klasse. Bij meervoudige overerving zijn er meerdere parent-klassen. Een child-klasse kan erven van 2, 3, 10, enz. parent-klassen.
Hier worden de voordelen van super duidelijker. Naast het besparen van toetsaanslagen door niet telkens verschillende parent-klassenamen te hoeven refereren, zijn er subtiele voordelen aan het gebruik van super bij patronen van meervoudige overerving. Kort gezegd: als je meervoudige overerving gaat gebruiken, gebruik dan super.
Meervoudige overerving zonder super
Laten we kijken naar een voorbeeld van meervoudige overerving dat het wijzigen van parent-methoden vermijdt en daarmee ook super vermijdt.
Input
class B:
def b(self):
print('b')
class C:
def c(self):
print('c')
class D(B, C):
def d(self):
print('d')
d = D()
d.b()
d.c()
d.d()
Output
b
c
d
Multiple-resolution order
Deze output is niet heel verrassend gezien het concept van meervoudige overerving. D erfde de methoden x en z van zijn parent-klassen, en alles is prima… voorlopig.
Maar wat als zowel B als C een methode met dezelfde naam hadden? Dan komt het concept ‘multiple-resolution order’ om de hoek kijken, of kortweg MRO. De MRO van een child-klasse bepaalt waar Python naar een bepaalde methode zoekt en welke methode wordt aangeroepen wanneer er een conflict is.
Laten we een voorbeeld bekijken.
Input
class B:
def x(self):
print('x: B')
class C:
def x(self):
print('x: C')
class D(B, C):
pass
d = D()
d.x()
print(D.mro())
Output
x: B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
Wanneer we de geërfde x-methode aanroepen, zien we alleen de output die is geërfd van B. We kunnen de MRO van onze D-klasse zien door de klassemethode mro aan te roepen. Uit de output van D.mro() leren we het volgende: ons programma probeert standaard D-methoden aan te roepen, valt dan terug op B, daarna op C, en tenslotte op object. Als het daar nergens wordt gevonden, krijgen we de fout dat D de gevraagde methode niet heeft.
Het is vermeldenswaard dat standaard elke klasse erft van object, en dat staat altijd achteraan in elke MRO.
Meervoudige overerving, super en het diamond-probleem
Hieronder staat een voorbeeld van het gebruik van super om de MRO van init op een nuttige manier af te handelen. In het voorbeeld maken we een reeks tekstverwerkingsklassen en combineren we hun functionaliteit in een andere klasse met meervoudige overerving. We maken 4 klassen en de overervingsstructuur volgt het diagram hieronder.
Opmerking: deze structuur is ter illustratie; afgezien van beperkingen zijn er betere manieren om deze logica te implementeren.

Dit is eigenlijk een voorbeeld van het ‘diamond-probleem’ bij meervoudige overerving. De naam is uiteraard gebaseerd op de vorm van het ontwerp en het feit dat het een behoorlijk verwarrend probleem is.
Hieronder is het ontwerp uitgeschreven met gebruik van super.
Input
class Tokenizer:
"""Tokenize text"""
def __init__(self, text):
print('Start Tokenizer.__init__()')
self.tokens = text.split()
print('End Tokenizer.__init__()')
class WordCounter(Tokenizer):
"""Count words in text"""
def __init__(self, text):
print('Start WordCounter.__init__()')
super().__init__(text)
self.word_count = len(self.tokens)
print('End WordCounter.__init__()')
class Vocabulary(Tokenizer):
"""Find unique words in text"""
def __init__(self, text):
print('Start init Vocabulary.__init__()')
super().__init__(text)
self.vocab = set(self.tokens)
print('End init Vocabulary.__init__()')
class TextDescriber(WordCounter, Vocabulary):
"""Describe text with multiple metrics"""
def __init__(self, text):
print('Start init TextDescriber.__init__()')
super().__init__(text)
print('End init TextDescriber.__init__()')
td = TextDescriber('row row row your boat')
print('--------')
print(td.tokens)
print(td.vocab)
print(td.word_count)
Output
Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
--------
['row', 'row', 'row', 'your', 'boat']
{'boat', 'your', 'row'}
5
Allereerst zien we dat de TextDescriber-klasse alle attributen van de klassenstamboom heeft geërfd. Dankzij meervoudige overerving kunnen we de functionaliteit van meer dan één klasse ‘combineren’.
Laten we nu de printregels bespreken die uit de init-methoden van de klassen kwamen:
Elke __init__-methode is precies één keer aangeroepen.
De TextDescriber-klasse erft van 2 klassen die van Tokenizer erven. Waarom is Tokenizer.__init__ niet twee keer aangeroepen?
Als we alle aanroepen van super zouden vervangen door de ouderwetse manier, zouden we 2 aanroepen naar Tokenizer.__init__ krijgen. De aanroepen via super ‘denken’ iets beter mee met ons patroon en slaan het extra rondje langs A over.
Elke __init__-methode begon voordat een andere was afgerond.
De volgorde van de starts en finishes van elke __init__ is het vermelden waard als je een attribuut probeert te zetten dat een naamconflict heeft met een andere parent-klasse. Het attribuut wordt dan overschreven, en dat kan erg verwarrend worden.
In ons geval hebben we naamconflicten met geërfde attributen vermeden, dus alles werkt zoals verwacht.
Nogmaals: het diamond-probleem kan snel ingewikkeld worden en tot onverwachte resultaten leiden. Zoals bij de meeste programmeergevallen is het het beste om complexe ontwerpen te vermijden.
Wat we hebben geleerd
- We leerden over de functie
superen hoe dieParentName.methodkan vervangen bij enkelvoudige overerving. Dit kan onderhoudsvriendelijker zijn. - We leerden over meervoudige overerving en hoe we de functionaliteit van meerdere parent-klassen kunnen doorgeven aan één child-klasse.
- We leerden over multiple-resolution order en hoe dat bepaalt wat er gebeurt bij meervoudige overerving wanneer er een naamconflict is tussen parent-methoden.
- We leerden over het diamond-probleem en zagen een voorbeeld van hoe het gebruik van
superhet diamond navigeert.

Korte introductie tot overerving
Naarmate je Python-projecten en -packages groeien, wil je onvermijdelijk klassen gebruiken en daarbij het DRY-principe (don’t repeat yourself) toepassen. Klassenovererving is een geweldige manier om een klasse te maken op basis van een andere klasse om DRY te blijven. Deze post behandelt meer geavanceerde concepten van overerving; basisovererving komt niet uitgebreid aan bod. We doen een korte intro, maar er zijn veel betere, gedetailleerde introducties te vinden. Hier zijn wat bronnen om te beginnen: de cursus Object-Oriented Programming in Python & Python Object-Oriented Programming (OOP): Tutorial.
Wat is klassenovererving precies? Net als bij genetica kan een child-klasse attributen en methoden ‘erven’ van een parent. Laten we meteen in wat code duiken voor een voorbeeld. In het onderstaande codeblok laten we overerving zien met een
Child-klasse die erft van eenParent-klasse.Input
Output
We zien dat de
Child-klasse attributen en methoden ‘erft’ van deParent-klasse. Zonder extra werk aan onze kant maaktParent.parent_methoddeel uit van deChild-klasse. Om de voordelen van deParent.__init__()-methode te krijgen, moesten we de methode expliciet aanroepen enselfdoorgeven. Dat komt doordat we bij het toevoegen van een__init__-methode aanChildde geërfde__init__overschreven.Met die korte, allerminst volledige intro uit de weg, duiken we nu in de kern van de post.
Intro tot
superIn het simpelste geval kan de functie
superworden gebruikt om de expliciete aanroepParent.__init__(self)te vervangen. Ons introvoorbeeld uit de eerste sectie kan metsuperworden herschreven zoals hieronder. Let op: het onderstaande codeblok is geschreven in Python 3; eerdere versies gebruiken een iets andere syntax. Daarnaast is de output weggelaten omdat die identiek is aan het eerste codeblok.Eerlijk gezegd levert
superin dit geval weinig tot geen voordeel op. Afhankelijk van de naam van onze parent-klasse besparen we misschien wat toetsaanslagen, en we hoevenselfniet door te geven aan de aanroep van__init__. Hieronder staan enkele voor- en nadelen van het gebruik vansuperbij enkelvoudige overerving.Nadelen
Je zou kunnen stellen dat het gebruik van
superde code hier minder expliciet maakt. Code minder expliciet maken druist in tegen The Zen of Python, waarin staat: “Explicit is better than implicit.”Voordelen
Er is een onderhoudbaarheidsargument te maken voor
super, zelfs bij enkelvoudige overerving. Als om wat voor reden dan ook je child-klasse haar overervingspatroon wijzigt (bijv. de parent-klasse verandert of er wordt overgestapt op meervoudige overerving), dan hoef je niet overal de verwijzingen naarParentClass.method_name()te zoeken en te vervangen; metsupervloeien de wijzigingen automatisch door dankzij de verandering in declass-verklaring.