Opstap¶
Operatoren en exceptions¶
Leerdoel: een eigen klasse laten werken met == en < door een magische
methode te schrijven, een fout melden met raise, een exception afvangen met
try/except, en een superklasse laten afdwingen dat een subklasse een methode
overschrijft.
Deze opdrachten zijn kaal met opzet. Ze oefenen de handgrepen die je bij de basisopgave en het practicum van deze week nodig hebt, zodat je daar met het probleem bezig kunt zijn in plaats van met de syntaxis.
De opdrachten bouwen voort op de klassen Lamp en DimmableLamp uit de opstap
van week 6. Voer de cel hieronder eerst uit. In deze cel voeg je in opdracht 2,
3 en 4 een methode toe; voer haar daarna steeds opnieuw uit, en dan de testcel
van die opdracht.
class Lamp:
"""Een lamp die aan of uit staat."""
def __init__(self, color):
"""Maak een lamp met de gegeven kleur; hij staat uit."""
self.color = color
self.on = False
def __repr__(self):
"""Geeft de lamp als string, met de naam van de klasse en de kleur."""
return f"{self.__class__.__name__}({self.color})"
def switch(self):
"""Zet de lamp aan als hij uit staat, en uit als hij aan staat."""
self.on = not self.on
def describe(self):
"""Geeft een korte beschrijving, zoals lamp rood: uit."""
if self.on:
return f"lamp {self.color}: aan"
return f"lamp {self.color}: uit"
class DimmableLamp(Lamp):
"""Een lamp die je kunt dimmen."""
def __init__(self, color, level=100):
"""Maak een dimbare lamp; zonder level brandt hij op 100%."""
super().__init__(color)
self.level = level
def describe(self):
"""Geeft de beschrijving van een lamp, met de helderheid erachter."""
return super().describe() + f", {self.level}%"
Opdracht 1¶
Voorspel eerst wat deze code afdrukt, en voer haar daarna uit:
a = Lamp("rood")
b = Lamp("rood")
print(a == b)
print(a is b)
Beantwoord daarna als commentaar in de cel: waarom geeft a == b dit antwoord,
terwijl de twee lampen dezelfde kleur hebben en allebei uit staan? Denk aan wat
== in week 5 bij je eigen klassen vergeleek.
# voer de code hier uit
Opdracht 2¶
Je kunt zelf bepalen wat == bij een lamp betekent. Python vertaalt a == b
naar de aanroep a.__eq__(b). Een methode als __eq__, die Python zelf aanroept,
heet een magische methode. Je kent er al twee: __init__ en __repr__.
Geef Lamp een methode __eq__(self, other). Twee lampen zijn gelijk als ze
dezelfde kleur hebben en allebei aan of allebei uit staan. Is other geen lamp,
dan geeft __eq__ False.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
Hint
Begin met if not isinstance(other, Lamp): en geef in dat geval False terug.
isinstance ken je uit week 6.
Daarna vergelijk je self.color met other.color, en self.on met other.on.
# schrijf __eq__ in de cel met de klasse Lamp, en voer die cel opnieuw uit
a = Lamp("rood")
b = Lamp("rood")
assert a == b
assert a is not b
assert not a == Lamp("blauw")
a.switch()
assert not a == b
assert a != b
assert not Lamp("rood") == "rood"
Kijk naar de tweede assertion: a == b is nu True, en a is b blijft
False. == vergelijkt voortaan de waarde, is nog steeds de identiteit.
Ook != werkt, zonder dat je het schrijft: Python keert het antwoord van
__eq__ om.
Opdracht 3¶
Python vertaalt a < b naar a.__lt__(b), met lt van less than. Geef
DimmableLamp een methode __lt__(self, other): een lamp is kleiner dan een
andere als hij minder fel brandt.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
Is other geen DimmableLamp, dan valt er niets te vergelijken. Gooi dan een
exception, met deze regel:
raise TypeError("een DimmableLamp is alleen met een DimmableLamp te vergelijken")
raise gooit een exception: Python stopt met de methode en toont een
foutmelding. TypeError is de soort fout voor een waarde van het verkeerde
type. In opdracht 5 zie je wat raise precies doet.
Heb je __lt__, dan kan sorted een lijst lampen sorteren, want sorted
vergelijkt met <.
# schrijf __lt__ in de klasse DimmableLamp, en voer de cel opnieuw uit
dim = DimmableLamp("wit", 30)
bright = DimmableLamp("wit", 80)
assert dim < bright
assert not bright < dim
assert not dim < DimmableLamp("geel", 30)
lamps = [DimmableLamp("rood", 60), DimmableLamp("wit", 10), DimmableLamp("geel", 100)]
assert [lamp.level for lamp in sorted(lamps)] == [10, 60, 100]
Opdracht 4¶
Een helderheid van 150% of -5% bestaat niet. Laat de constructor van
DimmableLamp zo’n lamp weigeren: is level kleiner dan 0 of groter dan
100, dan gooit hij een ValueError. Dat is de soort fout voor een waarde van
het goede type die toch niet kan.
raise ValueError("de helderheid moet van 0 tot en met 100 zijn")
Aanroep |
Resultaat |
|---|---|
|
een lamp met helderheid |
|
een |
# pas de constructor van DimmableLamp aan, en voer de cel opnieuw uit
assert DimmableLamp("wit", 0).level == 0
assert DimmableLamp("wit", 100).level == 100
assert DimmableLamp("wit").level == 100
assert DimmableLamp("wit", 30).describe() == "lamp wit: uit, 30%"
Opdracht 5¶
Voorspel eerst wat deze code doet, en voer haar daarna uit:
lamp = DimmableLamp("wit", 150)
print("gelukt")
Beantwoord als commentaar in de cel: wordt gelukt afgedrukt? Wat staat er op
de laatste regel van de foutmelding, en waar komt die tekst vandaan?
# voer de code hier uit
Opdracht 6¶
Een exception hoeft je programma niet te laten stoppen. Met try en except
vang je haar af:
try:
lamp = DimmableLamp("wit", 150)
print("gelukt")
except ValueError:
print("geen geldige helderheid")
print("klaar")
Python voert het blok onder try uit. Gooit een regel daarin een ValueError,
dan slaat Python de rest van dat blok over en voert het blok onder except uit.
Daarna gaat het programma gewoon verder. Gooit niets een exception, dan wordt
het except-blok overgeslagen.
Voorspel eerst wat de code afdrukt, en voer haar daarna uit. Verander daarna
150 in 50: wat drukt ze dan af?
# voer de code hier uit
Opdracht 7¶
Schrijf een functie make_lamps(color, levels). Ze geeft een lijst dimbare
lampen in de kleur color terug, één voor elke helderheid in de lijst levels.
Een helderheid die de constructor weigert, slaat de functie over, en ze drukt
overgeslagen: af met die helderheid erachter. Zo handel je de fout af:
het programma doet iets zinnigs in plaats van te stoppen.
Aanroep |
Geeft terug |
Drukt af |
|---|---|---|
|
lampen met helderheid |
|
|
|
niets |
Hint
Zet de try binnen de lus, om het maken van één lamp. Dan gaat de lus na een
fout verder met de volgende helderheid.
# jouw oplossing
lamps = make_lamps("wit", [30, 150, 0, -5, 100])
assert [lamp.level for lamp in lamps] == [30, 0, 100]
assert make_lamps("rood", []) == []
Opdracht 8¶
In week 6 viel een subklasse die een methode niet overschreef, stilletjes terug
op de versie van de superklasse. Een superklasse kan ook afdwingen dat elke
subklasse een methode zelf schrijft: haar eigen versie gooit dan een
NotImplementedError.
Voorspel eerst wat deze code doet, en voer haar daarna uit:
class LightSource:
"""Iets dat licht geeft. Elke subklasse moet describe overschrijven."""
def describe(self):
"""Gooit een NotImplementedError: een subklasse moet dit zelf schrijven."""
name = self.__class__.__name__
raise NotImplementedError(f"{name} moet describe overschrijven")
class Candle(LightSource):
"""Een kaars."""
def describe(self):
"""Geeft een korte beschrijving van de kaars."""
return "kaars"
class Torch(LightSource):
"""Een zaklamp, die vergeet describe te overschrijven."""
print(Candle().describe())
print(Torch().describe())
Beantwoord daarna als commentaar in de cel: welke describe draait er bij de
kaars, en welke bij de zaklamp? Welke klassenaam staat in de foutmelding, en
waarom die?
# voer de code hier uit
Opdracht 9¶
DimmableLamp heeft __lt__ voor <, maar geen methode voor >. Voorspel
eerst wat deze code doet, en voer haar daarna uit:
dim = DimmableLamp("wit", 30)
bright = DimmableLamp("wit", 80)
print(bright > dim)
print(dim > bright)
Beantwoord als commentaar in de cel: hoe kan Python bright > dim beantwoorden
zonder methode voor >? Denk aan wat bright > dim betekent als je het omdraait.
# voer de code hier uit
Opdracht 10¶
Voorspel eerst wat deze code doet, en voer haar daarna uit:
print(DimmableLamp("wit", 30) == "wit")
print(DimmableLamp("wit", 30) < Lamp("rood"))
Beantwoord als commentaar in de cel: waarom geeft de eerste regel een antwoord, en de tweede een foutmelding? Welke methode gooit die exception?
# voer de code hier uit