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, een klasse schrijven die door duck typing meedoet zonder subklasse te zijn, en vragen van welke klasse een object is.

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

Opdracht 12

Een object weet zelf van welke klasse het is. x.__class__ is de klasse van x, en x.__class__.__name__ de naam van die klasse, als string. Met isinstance(x, K) vraag je of x een object is van de klasse K, of van een subklasse van K.

Voorspel eerst wat deze lus afdrukt, en voer haar daarna uit. De lampen en de kaars komen uit de opdrachten hiervoor.

things = [Lamp("rood"), DimmableLamp("wit", 30), ReadingLamp("groen"), Candle()]
for thing in things:
    print(
        thing.__class__.__name__,
        isinstance(thing, Lamp),
        isinstance(thing, DimmableLamp),
    )

Beantwoord daarna als commentaar in de cel:

  1. Waarom is een leeslamp volgens isinstance een Lamp, en een kaars niet?

  2. Stel dat de lus in de test van opdracht 10 met isinstance(thing, Lamp) alleen lampen liet meedoen. Wat gebeurt er dan met de kaars, die wel switch en describe heeft? Wat betekent dat voor duck typing?

# voer de code hier uit

Opdracht 13

Geef Lamp een methode __repr__, zodat een lamp zich afdrukt als de naam van zijn klasse met de kleur erachter, zoals Lamp(rood). Schrijf de klassenaam niet zelf in de tekst, maar gebruik self.__class__.__name__. Dan drukt elke subklasse haar eigen naam af, zonder eigen __repr__.

Aanroep

Resultaat

repr(Lamp("rood"))

"Lamp(rood)"

repr(DimmableLamp("wit", 30))

"DimmableLamp(wit)"

repr(ReadingLamp("groen"))

"ReadingLamp(groen)"

Voer daarna de cel met Lamp opnieuw uit, en ook de cellen van opdracht 3 en 8, zodat DimmableLamp en ReadingLamp van de nieuwe Lamp erven. Voer dan de test hieronder uit.

Hint

Gebruik een f-string met self.__class__.__name__ en self.color.

# schrijf __repr__ in de cel met de klasse Lamp
assert repr(Lamp("rood")) == "Lamp(rood)"
assert repr(DimmableLamp("wit", 30)) == "DimmableLamp(wit)"
assert repr(ReadingLamp("groen")) == "ReadingLamp(groen)"