Opstap

Deze pagina bevat uitvoerbare code.

Overerving

Leerdoel: een subklasse schrijven die met super() voortbouwt op haar superklasse, een methode overschrijven, een parameter een standaardwaarde geven, voorspellen welke versie van een methode er draait bij een lijst van verschillende objecten, en een klasse schrijven die door duck typing meedoet zonder subklasse te zijn.

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 klasse Lamp uit de opstap van week 5, met één methode erbij: describe, die een korte beschrijving teruggeeft. Voer de cel hieronder eerst uit.

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

Opdracht 1

Voorspel eerst wat deze code afdrukt, en voer haar daarna uit:

class NightLamp(Lamp):
    """Een nachtlampje."""


n = NightLamp("geel")
n.switch()
print(n.on)
print(n.describe())

class NightLamp(Lamp): maakt NightLamp een subklasse van Lamp, en Lamp de superklasse van NightLamp. De klasse bevat alleen een docstring.

# voer de code hier uit

Opdracht 2

Beantwoord als commentaar in de cel hieronder: in NightLamp staat geen enkele methode. Waar komen __init__, switch en describe van een nachtlampje dan vandaan?

# jouw antwoord

Opdracht 3

Schrijf een subklasse DimmableLamp van Lamp: een lamp die je kunt dimmen. Haar constructor krijgt twee argumenten, color en level. Hij laat de constructor van Lamp het werk voor color doen, en slaat level op in het attribuut self.level.

Aanroep

Resultaat

DimmableLamp("wit", 30).color

"wit"

DimmableLamp("wit", 30).on

False

DimmableLamp("wit", 30).level

30

Hint

super().__init__(color) roept de constructor van de superklasse aan. Die geeft self.color en self.on hun beginwaarde. Daarna zet je zelf self.level.

# jouw oplossing
d = DimmableLamp("wit", 30)
assert d.color == "wit"
assert not d.on
assert d.level == 30
d.switch()
assert d.on

Opdracht 4

Een dimbare lamp brandt meestal op volle sterkte. Geef de parameter level een standaardwaarde: schrijf level=100 in plaats van level. Laat je het argument dan weg, dan krijgt level de waarde 100. Voer daarna de cel van opdracht 3 opnieuw uit, en dan de test hieronder.

Aanroep

Resultaat

DimmableLamp("wit").level

100

DimmableLamp("wit", 30).level

30

assert DimmableLamp("wit").level == 100
assert DimmableLamp("wit", 30).level == 30
assert DimmableLamp("wit", 0).level == 0

Opdracht 5

Geef DimmableLamp een eigen methode describe. Die geeft wat describe van Lamp teruggeeft, met de helderheid erachter. DimmableLamp overschrijft zo de methode describe van Lamp. Voer daarna de cel van opdracht 3 opnieuw uit, en dan de test hieronder.

Code

describe() geeft

DimmableLamp("wit", 30)

"lamp wit: uit, 30%"

daarna switch()

"lamp wit: aan, 30%"

Hint

Schrijf de tekst van Lamp niet opnieuw. super().describe() roept de methode describe van de superklasse aan en geeft haar tekst terug; daar plak je de helderheid achter.

d = DimmableLamp("wit", 30)
assert d.describe() == "lamp wit: uit, 30%"
d.switch()
assert d.describe() == "lamp wit: aan, 30%"
assert DimmableLamp("blauw").describe() == "lamp blauw: uit, 100%"

Opdracht 6

Deze subklasse heeft een constructor die super().__init__ niet aanroept. Voorspel eerst wat de code afdrukt, en voer haar daarna uit:

class BrokenLamp(Lamp):
    """Een lamp met een constructor die iets vergeet."""

    def __init__(self, color, level):
        """Maak een lamp met de gegeven kleur en helderheid."""
        self.level = level


b = BrokenLamp("rood", 50)
print(b.level)
print(b.describe())
# voer de code hier uit

Beantwoord als commentaar in de cel hieronder: welke attributen ontbreken er, en waarom? De foutmelding noemt er maar één. En wat moet er in de constructor bij?

# jouw antwoord

Opdracht 7

Schrijf een subklasse CountingLamp van Lamp: een lamp die telt hoe vaak hij is omgezet.

Methode

Doet

__init__(self, color)

doet wat de constructor van Lamp doet, en zet self.count op 0

switch(self)

zet de lamp om zoals switch van Lamp dat doet, en telt 1 op bij self.count

Hint

In switch roep je eerst super().switch() aan. Zo schrijf je het omzetten zelf niet opnieuw.

# jouw oplossing
c = CountingLamp("blauw")
assert c.count == 0
assert not c.on
c.switch()
c.switch()
c.switch()
assert c.count == 3
assert c.on
assert c.describe() == "lamp blauw: aan"

Opdracht 8

Een leeslamp is een dimbare lamp die op 60% staat. Voorspel eerst wat deze code afdrukt, en voer haar daarna uit:

class ReadingLamp(DimmableLamp):
    """Een leeslamp: een dimbare lamp die op 60% staat."""

    def __init__(self, color):
        """Maak een leeslamp op 60%."""
        super().__init__(color, 60)


r = ReadingLamp("groen")
r.switch()
print(r.describe())

Beantwoord daarna als commentaar in de cel: welke describe draait er bij r, die van Lamp of die van DimmableLamp? En welke van de twee zet lamp groen: aan in de tekst?

# voer de code hier uit

Opdracht 9

Hieronder staat een lijst met vier verschillende lampen. Voorspel eerst wat de lus afdrukt, en voer haar daarna uit:

lamps = [Lamp("rood"), DimmableLamp("wit", 30), NightLamp("geel"), ReadingLamp("groen")]
for lamp in lamps:
    lamp.switch()
    print(lamp.describe())

In de lus staat één aanroep, lamp.describe(). Welke versie van describe er draait, hangt af van het object. Dat heet polymorfisme.

# voer de code hier uit

Opdracht 10

Een kaars is geen lamp: hij heeft geen kleur en brandt of brandt niet. Schrijf een klasse Candle die geen subklasse van Lamp is, maar wel dezelfde twee methoden heeft als een lamp, zodat hij mee kan doen in een lus als die van opdracht 9.

Methode

Doet

__init__(self)

maakt een kaars die niet brandt: self.lit is False

switch(self)

steekt de kaars aan als hij uit is, en blaast hem uit als hij brandt

describe(self)

geeft "kaars: brandt" of "kaars: uit"

# jouw oplossing
k = Candle()
assert k.describe() == "kaars: uit"
k.switch()
assert k.describe() == "kaars: brandt"
things = [Lamp("rood"), Candle(), DimmableLamp("wit", 30)]
for thing in things:
    thing.switch()
assert [thing.describe() for thing in things] == [
    "lamp rood: aan",
    "kaars: brandt",
    "lamp wit: aan, 30%",
]

Opdracht 11

Beantwoord als commentaar in de cel hieronder:

  1. Candle erft niets van Lamp. Waarom werkt de lus in de test van opdracht 10 dan toch?

  2. Wat gebeurt er als je een object in de lijst zet dat geen describe heeft? Op welk moment merk je dat?

Dat een object alleen de methoden en attributen hoeft te hebben die worden gebruikt, en niet van een bepaalde klasse hoeft te zijn, heet duck typing: als het loopt als een eend en kwaakt als een eend, dan is het een eend.

# jouw antwoord