Overerving¶
Voortbouwen op een klasse die er al is
Waar we waren¶
In week 5 schreef je de klasse Student, met een afgeschermd startjaar. Hier is
ze weer, met één methode erbij: graduation_year, het jaar waarin de student naar
verwachting afstudeert. Een voltijdstudent doet er vier jaar over.
class Student:
"""Een student met een naam en een startjaar."""
def __init__(self, name, year):
"""Maak een student met de gegeven naam en het gegeven startjaar."""
self.name = name
self._year = year
def __repr__(self):
"""Geeft de student als string, om af te drukken."""
return "naam: " + self.name + ", startjaar: " + str(self._year)
def delay(self, num_years):
"""Stel de start van de studie num_years jaar uit, en nooit terug."""
if num_years > 0:
self._year += num_years
@property
def year(self):
"""Het startjaar, alleen om te lezen."""
return self._year
def graduation_year(self):
"""Geeft het jaar waarin de student naar verwachting afstudeert."""
return self._year + 4
student_a = Student("Sanne de Wit", 2024)
print(student_a)
print(student_a.graduation_year())
naam: Sanne de Wit, startjaar: 2024
2028
Een deeltijdstudent¶
Een deeltijdstudent studeert naast zijn werk. Hij heeft een naam en een startjaar, hij kan zijn studie uitstellen, en hij wordt net zo afgedrukt. Alleen doet hij er langer over: zes jaar in plaats van vier.
Je zou de klasse Student kunnen kopiëren en er PartTimeStudent van maken. Dan
staan __init__, delay en year twee keer in je programma. Zit er een fout in
delay, dan moet je hem op twee plekken herstellen, en vergeet je er een, dan
gedragen de twee soorten studenten zich ineens anders.
Een subklasse¶
Het kan ook zonder kopiëren. Je schrijft achter de naam van de nieuwe klasse, tussen haakjes, de klasse waarop ze voortbouwt:
class PartTimeStudent(Student):
"""Een deeltijdstudent, die naast zijn werk studeert."""
Deze klasse bevat alleen een docstring. Toch kan een deeltijdstudent alles wat een student kan:
student_b = PartTimeStudent("Ali Bakker", 2024)
print(student_b)
student_b.delay(1)
print(student_b.year)
naam: Ali Bakker, startjaar: 2024
2025
PartTimeStudent("Ali Bakker", 2024) roept de constructor van Student aan,
print gebruikt __repr__ van Student, en delay en year komen ook uit
Student. Een deeltijdstudent is een student, en krijgt daarom alles mee wat
een student heeft.
De begrippen op een rij:
Begrip |
Wat het is |
Hier |
|---|---|---|
overerving |
een klasse krijgt de attributen en methoden van een andere klasse mee |
|
subklasse |
de klasse die erft |
|
superklasse |
de klasse waarvan geërfd wordt |
|
Een methode overschrijven¶
Eén ding klopt nog niet:
print(student_b.graduation_year())
2029
Een deeltijdstudent doet er zes jaar over, geen vier. De subklasse krijgt daarom
een eigen versie van graduation_year:
class PartTimeStudent(Student):
"""Een deeltijdstudent, die naast zijn werk studeert."""
def graduation_year(self):
"""Geeft het verwachte afstudeerjaar: na zes jaar."""
return self.year + 6
student_b = PartTimeStudent("Ali Bakker", 2024)
print(student_b.graduation_year())
print(student_a.graduation_year())
2030
2028
PartTimeStudent overschrijft de methode graduation_year van Student
(in het Engels: override). Bij een deeltijdstudent draait nu de versie van
PartTimeStudent, bij een gewone student nog steeds die van Student. Python
zoekt een methode eerst in de klasse van het object zelf, en pas als ze daar niet
staat in de superklasse.
super()¶
Zes jaar is “twee jaar langer dan voltijd”. Staat dat zo in de code, dan klopt een
deeltijdstudent ook nog als de voltijdse studieduur ooit verandert. Met super()
roep je de versie van de superklasse aan:
class PartTimeStudent(Student):
"""Een deeltijdstudent, die naast zijn werk studeert."""
def graduation_year(self):
"""Geeft het verwachte afstudeerjaar: twee jaar later dan voltijd."""
return super().graduation_year() + 2
student_b = PartTimeStudent("Ali Bakker", 2024)
print(student_b.graduation_year())
2030
super().graduation_year() draait graduation_year van Student, voor hetzelfde
object: hier 2028. De subklasse zegt alleen wat er anders is: twee jaar erbij.
De constructor uitbreiden¶
Een uitwisselingsstudent komt een tijdje van een andere universiteit. Hij heeft
alles wat een student heeft, en één attribuut erbij: zijn eigen universiteit. De
subklasse krijgt daarom een eigen constructor, en die roept eerst de constructor
van Student aan:
class ExchangeStudent(Student):
"""Een uitwisselingsstudent, die van een andere universiteit komt."""
def __init__(self, name, year, university):
"""Maak een uitwisselingsstudent van de gegeven universiteit."""
super().__init__(name, year)
self.university = university
def __repr__(self):
"""Geeft de student als string, met de universiteit erbij."""
return super().__repr__() + ", van: " + self.university
student_c = ExchangeStudent("Lotte Smit", 2025, "Gent")
print(student_c)
print(student_c.graduation_year())
naam: Lotte Smit, startjaar: 2025, van: Gent
2029
super().__init__(name, year) laat Student doen wat Student al kan: name en
_year een beginwaarde geven. Daarna zet de constructor van ExchangeStudent het
nieuwe attribuut. Ook __repr__ bouwt voort op de versie van Student, via
super().__repr__().
Als super().__init__ ontbreekt¶
Een constructor in een subklasse overschrijft de constructor van de superklasse.
Roept hij super().__init__ niet aan, dan draait de constructor van Student dus
helemaal niet:
class ForgetfulStudent(Student):
"""Een subklasse waarvan de constructor iets vergeet."""
def __init__(self, name, year, university):
"""Maak een student, maar zonder de constructor van Student."""
self.university = university
f = ForgetfulStudent("Tom Jansen", 2024, "Gent")
print(f)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[12], line 10
6 self.university = university
7
8
9 f = ForgetfulStudent("Tom Jansen", 2024, "Gent")
---> 10 print(f)
Cell In[1], line 11, in Student.__repr__(self)
9 def __repr__(self):
10 """Geeft de student als string, om af te drukken."""
---> 11 return "naam: " + self.name + ", startjaar: " + str(self._year)
AttributeError: 'ForgetfulStudent' object has no attribute 'name'
__repr__ van Student leest self.name, en dat attribuut heeft niemand een
waarde gegeven. Python meldt een AttributeError, en pas op het moment dat het
attribuut nodig is: het object maken ging nog goed.
Een standaardwaarde voor een parameter¶
Een parameter kan een standaardwaarde krijgen: een waarde die hij krijgt als
je het argument bij de aanroep weglaat. Je schrijft haar met = in de signatuur:
def greet(name, greeting="Hallo"):
"""Geeft een begroeting voor name, standaard met Hallo."""
return greeting + ", " + name + "!"
print(greet("Lotte"))
print(greet("Lotte", "Goedemorgen"))
Hallo, Lotte!
Goedemorgen, Lotte!
Geef je het argument mee, dan telt wat je meegeeft. Laat je het weg, dan geldt de standaardwaarde. Parameters met een standaardwaarde staan achteraan: eerst de parameters die je altijd moet meegeven, dan die je mag weglaten.
Een uitwisselingsstudent blijft meestal één semester, vijf maanden. Dat kan een standaardwaarde zijn:
class ExchangeStudent(Student):
"""Een uitwisselingsstudent, die van een andere universiteit komt."""
def __init__(self, name, year, university, months=5):
"""Maak een uitwisselingsstudent; zonder months blijft hij één semester."""
super().__init__(name, year)
self.university = university
self.months = months
def __repr__(self):
"""Geeft de student als string, met de universiteit erbij."""
return super().__repr__() + ", van: " + self.university
student_c = ExchangeStudent("Lotte Smit", 2025, "Gent")
student_d = ExchangeStudent("Noor Visser", 2025, "Lissabon", 10)
print(student_c.months)
print(student_d.months)
5
10
Op een rij¶
Begrip |
Wat het is |
Hier |
|---|---|---|
overerving |
een klasse krijgt de attributen en methoden van een andere klasse mee |
|
subklasse |
de klasse die erft |
|
superklasse |
de klasse waarvan geërfd wordt |
|
overschrijven |
een subklasse geeft een methode een eigen versie |
|
|
roept de versie van de superklasse aan |
|
standaardwaarde |
de waarde van een parameter als het argument ontbreekt |
|
Een subklasse schrijft alleen wat anders is. Wat ze niet zelf schrijft, erft ze.
Opdrachten¶
Opdracht 1¶
Schrijf een subklasse HonoursStudent van Student: een student die een extra
honoursprogramma volgt. Haar constructor krijgt naast name en year een
argument track, de naam van het programma, en slaat dat op in self.track.
Gebruik super().__init__.
Opdracht 2¶
Geef HonoursStudent een eigen __repr__, die de tekst van Student gebruikt
met het programma erachter, zoals naam: Sanne de Wit, startjaar: 2024, honours: data. Schrijf de tekst van Student niet opnieuw.
Opdracht 3¶
Geef de parameter track de standaardwaarde "algemeen". Controleer dat
HonoursStudent("Sanne de Wit", 2024).track dan "algemeen" is.
Opdracht 4¶
Voorspel eerst, en controleer daarna: verander in Student de 4 in
graduation_year in een 3, en voer alle cellen vanaf de klasse Student opnieuw
uit, in volgorde: een subklasse bouwt voort op de klasse Student die er stond
toen de subklasse werd gemaakt. Welk afstudeerjaar
geeft PartTimeStudent("Ali Bakker", 2024) nu, met de versie die super()
gebruikt? En wat had de versie met self.year + 6 gegeven?