Opstap

Deze pagina bevat uitvoerbare code.

Klassen

Leerdoel: een klasse schrijven met een constructor, attributen en methoden, een attribuut afschermen met _ en @property, een klasse schrijven waarvan de objecten andere objecten als onderdeel hebben, en met is nagaan of twee namen hetzelfde object aanwijzen.

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.

Opdracht 1

Voorspel eerst wat deze code afdrukt, en voer haar daarna 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


a = Lamp("rood")
b = Lamp("blauw")
a.switch()
print(a.on)
print(b.on)
# voer de code hier uit

Opdracht 2

Beantwoord als commentaar in de cel hieronder: hoeveel klassen en hoeveel objecten komen er in de code van opdracht 1 voor? En waarom drukt b.on iets anders af dan a.on, terwijl de klasse voor allebei dezelfde is?

# jouw antwoord

Opdracht 3

Schrijf een klasse Point met een constructor die twee argumenten krijgt, x en y, en die opslaat als de attributen self.x en self.y.

Aanroep

Resultaat

Point(3, 4).x

3

Point(3, 4).y

4

# jouw oplossing
p = Point(3, 4)
assert p.x == 3
assert p.y == 4

Opdracht 4

Voeg aan je klasse Point een methode __repr__ toe, zodat een punt wordt afgedrukt zoals je het zou aanmaken. Voer daarna de cel van opdracht 3 opnieuw uit, en dan de test hieronder.

Aanroep

Afgedrukt

print(Point(3, 4))

Point(3, 4)

repr(p) in de test geeft de string die __repr__ teruggeeft.

assert repr(Point(3, 4)) == "Point(3, 4)"
assert repr(Point(0, -2)) == "Point(0, -2)"

Opdracht 5

Voeg een methode distance_to_origin(self) toe. Die geeft de afstand van het punt tot het punt (0, 0): de wortel uit x * x + y * y. Voer daarna de cel van opdracht 3 opnieuw uit, en dan de test hieronder.

Aanroep

Resultaat

Point(3, 4).distance_to_origin()

5.0

Hint

De wortel van een getal is dat getal tot de macht 0.5: 25 ** 0.5 is 5.0. Binnen de methode kom je bij de coördinaten via self.

assert Point(3, 4).distance_to_origin() == 5.0
assert Point(0, 0).distance_to_origin() == 0.0

Opdracht 6

Voeg een methode move(self, dx, dy) toe. Die verschuift het punt: dx erbij bij x, en dy erbij bij y. De methode verandert het object en geeft niets terug. Voer daarna de cel van opdracht 3 opnieuw uit, en dan de test hieronder.

Code

Daarna

p = Point(3, 4) en dan p.move(1, -4)

p is Point(4, 0)

p = Point(3, 4)
p.move(1, -4)
assert repr(p) == "Point(4, 0)"
q = Point(3, 4)
assert repr(q) == "Point(3, 4)"

Opdracht 7

Neem de klasse Square hieronder over in de cel. Maak een vierkant met s = Square(3) en druk s.side af.

class Square:
    """Een vierkant waarvan de zijde alleen groter kan worden."""

    def __init__(self, side):
        """Maak een vierkant met de gegeven zijde."""
        self._side = side

    @property
    def side(self):
        """De lengte van de zijde, alleen om te lezen."""
        return self._side

Voorspel daarna wat s.side = 5 doet, en probeer het. Wat zegt de foutmelding?

# jouw oplossing

Opdracht 8

Voeg aan Square een property area toe, die de oppervlakte teruggeeft. Je schrijft dus s.area, zonder haakjes.

Aanroep

Resultaat

Square(3).area

9

assert Square(3).area == 9
assert Square(0).area == 0

Opdracht 9

Voeg een methode grow(self, amount) toe. Die maakt de zijde amount langer, maar alleen als amount groter is dan 0. Anders gebeurt er niets.

Code

s.side daarna

s = Square(3) en dan s.grow(2)

5

daarna s.grow(-4)

nog steeds 5

Hint

grow staat zelf in de klasse, dus daar mag je self._side veranderen.

s = Square(3)
s.grow(2)
assert s.side == 5
s.grow(-4)
assert s.side == 5
s.grow(0)
assert s.side == 5

Opdracht 10

Beantwoord als commentaar in de cel hieronder: waarom kan een Square nu nooit kleiner worden, zolang je hem alleen via zijn methoden en properties gebruikt? En met welke regel code, van buiten de klasse, zou het tóch kunnen?

# jouw antwoord

Opdracht 11

Een figuur bestaat uit punten. Schrijf een klasse Figure die Point-objecten bevat en ze verschuift via hun eigen methode move. Gebruik je klasse Point van opdracht 3 tot en met 6.

Methode

Doet

__init__(self)

maakt een lege figuur: de lijst self._points is leeg

add(self, point)

voegt point toe aan de figuur

move(self, dx, dy)

verschuift elk punt van de figuur over dx en dy

__repr__(self)

geeft de figuur als string, zoals Figure([Point(0, 0), Point(3, 4)])

Code

Daarna

p = Point(0, 0), q = Point(3, 4), f = Figure(), f.add(p) en f.add(q)

f is Figure([Point(0, 0), Point(3, 4)])

daarna f.move(1, 1)

f is Figure([Point(1, 1), Point(4, 5)])

Hint

move van Figure roept voor elk punt in self._points de methode move van dat punt aan. Voor __repr__ helpt het dat een lijst wordt afgedrukt met de __repr__ van elk element: repr([Point(0, 0)]) is "[Point(0, 0)]".

# jouw oplossing

Voorspel eerst, voordat je de test hieronder leest of uitvoert. Schrijf als commentaar in de cel hieronder op: wat is p na f.move(1, 1)? En wat is f als je daarna p.move(-1, -1) aanroept? Voer pas daarna de test uit.

# jouw voorspelling
p = Point(0, 0)
q = Point(3, 4)
f = Figure()
f.add(p)
f.add(q)
assert repr(f) == "Figure([Point(0, 0), Point(3, 4)])"
f.move(1, 1)
assert repr(f) == "Figure([Point(1, 1), Point(4, 5)])"
assert repr(p) == "Point(1, 1)"
p.move(-1, -1)
assert repr(f) == "Figure([Point(0, 0), Point(4, 5)])"
empty = Figure()
empty.move(1, 1)
assert repr(empty) == "Figure([])"

Beantwoord als commentaar in de cel hieronder: waarom is p ook verschoven, terwijl je alleen f.move hebt aangeroepen? En waarom ziet f het, als je p van buiten de figuur verschuift?

# jouw antwoord

Opdracht 12

Een punt kan twee namen hebben. Schrijf in de cel hieronder, met je klasse Point van opdracht 3 tot en met 6:

  1. een punt p op (1, 2);

  2. een tweede naam q voor hetzelfde punt;

  3. een nieuw punt r, ook op (1, 2);

  4. een aanroep die het punt via q verschuift over 3 en 3.

Hint

Een nieuw object ontstaat alleen als je de klasse aanroept.

# jouw oplossing

Voorspel eerst, voordat je de test hieronder leest of uitvoert. Schrijf als commentaar in de cel hieronder op: wat drukken print(p) en print(r) af? En wat geven q is p en r is p? Voer pas daarna je cel en de test uit.

# jouw voorspelling
assert q is p
assert r is not p
assert repr(p) == "Point(4, 5)"
assert repr(q) == "Point(4, 5)"
assert repr(r) == "Point(1, 2)"

Beantwoord als commentaar in de cel hieronder: waarom is p verschoven, terwijl je move via q hebt aangeroepen? En vóór het verschuiven had r dezelfde coördinaten als p: wat had r == p toen gegeven, en waarom?

# jouw antwoord