Skip to content

Classes

Voor een snelle meting is het script dat je geschreven hebt bij opdracht Pythondaq: quick 'n dirty meting, opdracht Pythondaq: herhaalmetingen en opdracht Pythondaq: CSV prima! Maar als de meetapparatuur ingewikkelder wordt (meer verschillende commando's) of je wilt meer aanpassingen doen, dan is het wel lastig dat je op allerlei plekken de commando's opnieuw moet programmeren — en eerst moet opzoeken. Als je een nieuw script schrijft moet je opnieuw goed opletten dat je de goede terminator characters gebruikt, etc. Het is wat werk, maar toch heel handig, om je code op te splitsen en een class te schrijven.

Een class is eigenlijk een groep functies die je bij elkaar pakt en die met elkaar gegevens kunnen delen. Zodra een programma wat complexer wordt merk je dat het fijn kan zijn om variabelen op te sluiten in geïsoleerde omgevingen. We gaan eerst kijken hoe we een class gebruiken met behulp van de class Turtle. Met deze class kan je een tekening maken in Python, met behulp van functies geef je aan hoe de pen over het papier moet bewegen.

Aanroepen van een class

Het aanroepen van een class lijkt veel op het aanroepen van een functie:

result = calculate_squares_up_to(5)
master_oogway = Turtle("turtle") 

Stel je hebt de functie def calculate_squares_up_to(max_number):. Dan roep je die aan met result = calculate_squares_up_to(5). Hierbij is calculate_squares_up_to de naam van de functie en result de variabele waar de uitkomst heen gaat. Achter de naam van de functie, calculate_squares_up_to, komen tussen ronde haakjes de variabelen te staan die worden meegegeven aan de functie, in dit geval 5. Hierdoor wordt het kwadraat tot het getal 5 uitgerekend.

Bij het aanroepen van een class doe je iets soortgelijks. In de variabele master_oogway gaat de uitkomst van de class, dat is in dit geval een collectie van functies — de zogeheten methods van de class — (en variabelen). De variabele master_oogway noem je een instance van de class Turtle. Achter de naam van de class, Turtle, komen tussen ronde haakjes de variabelen te staan die worden meegegeven aan class, in dit geval "turtle". Hierdoor heeft de pen de vorm van een schildpad in plaats van een pijl.

Meerdere instances

Net zoals je een functie vaker kunt aanroepen, kan je ook meerdere instances van een class aanmaken. Je kunt meerdere instances hebben van dezelfde class, bijvoorbeeld voor verschillende schildpadden:

class Turtle:
    ...

turtle_1247 = Turtle()
...
turtle_1428 = Turtle()
...

De class Turtle is een verzameling van methods waaronder forward() en left(). Zoals hierboven staat beschreven zijn deze methods als uitkomst van de class meegegeven aan de instance master_oogway. Je kunt daarom de methods aanroepen met de instance master_oogway:

master_oogway = Turtle("turtle")

master_oogway.forward(50)
master_oogway.left(30)
master_oogway.forward(50)

Hierin is 50 het aantal stappen en 30 de hoek in graden die de schildpad tegen de klok in draait. Klik hier

De class Turtle

Je bent inmiddels nieuwsgierig geworden naar de schildpad. De class Turtle zit standaard in Python, daarom kan je deze importeren met from turtle import Turtle. Je opent in GitHub Desktop de repository oefenopdrachten en opent deze repository in Visual Studio Code. Je maakt een bestand Feynman.py aan. Aangezien er nog geen environment gekoppeld is aan deze repository maak je eerst een virtual environment aan (zie opdracht Virtual environment aanmaken). Teken nu met een schildpad met de instancenaam master_oogway een Feynman diagram.
ECPC
├── oefenopdrachten
           ├── Feynman.py
           └── •••
└── pythondaq
           └── •••

Scherm met schildpad verdwijnt

Na het uitvoeren van het script sluit Python het scherm van de schildpad. Voeg de regel master_oogway.screen.mainloop() toe om het scherm te laten staan en handmatig af te sluiten.

Pseudo-code

from turtle import Turtle

# create instance of class Turtle
master_oogway = Turtle("turtle")

# use forward() and left() to create a Feynman diagram
Voorbeeld uitkomst
screenshot Feynman diagram turtle

Checkpunten

  • De class Turtle wordt geïmporteerd uit de module turtle.
  • De instance is van de class Turtle met hoofdletter T.
  • De naam van de instance is master_oogway.
  • Om de schildpad te laten bewegen roep je de method forward() of left() van de instance aan.

Projecttraject

  • De class Turtle
  • De method __init__(self)
  • Een class maken
  • self = instance
  • Opbouw van een class

Het maken van een class

De class Turtle is behoorlijk complex en het gaat te ver om de class hier helemaal te gaan bespreken. Voor een goed begrip van classes ga je daarom kijken naar een versimpelde weergave van de class Turtle.

Een class maak je aan met de regel class Turtle:. 1 Daaronder komt ingesprongen de inhoud van de class. De class bestaat uit methods. De eerste method __init__() is een speciale method (voor meer informatie zie: dunder methods), dit is de initializer waarin alle taken staan die uitgevoerd worden zodra de class gebruikt wordt.

class Turtle:
    def __init__(self, shape):
        # transform turtle into shape

    def forward(self, distance):
        # move turtle by distance

    def left(self, angle):
        # turn turtle counterclockwise
        # by angle in degrees

De eerste parameter van de __init__()-method en van alle andere methods is self, daarna komen — indien nodig — andere parameters die in de method nodig zijn. Verder op in dit hoofdstuk leer je meer over de speciale parameter self.

Het aanmaken van een class lijkt in een aantal opzichten op het aanmaken van een functie:

def calculate_squares_up_to(max_number):
    squares = []
    for number in range(max_number):
        squares.append(number ** 2)
    return squares

result = calculate_squares_up_to(5)
class Turtle:
    def __init__(self, shape):
        # transform turtle into shape

    def forward(self, distance):
        # move turtle by distance

    def left(self, angle):
        # turn turtle counterclockwise
        # by angle in degrees

master_oogway = Turtle("turtle") 

De method __init__(self)

Achter de naam van de class: Turtle, komen tussen ronde haakjes de variabelen die worden meegegeven aan de __init__()-method (self niet meegerekend), de parameter shape krijgt dus de variabele "turtle" toegewezen.

Stel dat de __init__()-method geen extra parameters mee krijgt, zoals in onderstaand geval. Hoe maak je dan een instance aan van de class?

class Turtle:
    def __init__(self):
        # initialize class

    def forward(self, distance):
        # move turtle by distance

    def left(self, angle):
        # turn turtle counterclockwise
        # by angle in degrees

Uitwerkingen
master_oogway = Turtle()

Projecttraject

  • De class Turtle
  • De method __init__(self)
  • Een class maken
  • self = instance
  • Opbouw van een class

Een class maken

Je gaat de versimpelde weergave van de Turtle class gebruiken om een werkende class te maken die weliswaar geen schildpad laat lopen maar wel tekst print!

Maak een bestand fake-turtle.py waarin je zelf een class Turtle maakt. Zodra de class Turtle gebruikt wordt, print je tekst die aangeeft welke vorm de pen heeft. Wanneer de methods forward en left worden aangeroepen print je een tekst die aangeeft hoeveel stappen de turtle zet of hoeveel graden de turtle draait.
ECPC
├── oefenopdrachten
           ├── fake-turtle.py
           ├── Feynman.py
           └── •••
└── pythondaq
           └── •••

Pseudo-code

class Turtle:
    def __init__(self, shape):
        # print the shape of the turtle

    def forward(self, distance):
        # print the distance that the turtle moves

    def left(self, angle):
        # print the angle that the turtle turns counterclockwise
Testcode
fake_turtle.py
master_oogway = Turtle("turtle")

master_oogway.forward(50)
master_oogway.left(30)
master_oogway.forward(50)
(ECPC) > python fake_turtle.py

Checkpunten

  • Wanneer je een instance aanmaakt van de class Turtle moet je een parameter meegeven.
  • Wanneer je een instance aanmaakt van de class Turtle wordt in de terminal geprint welke vorm de turtle heeft.
  • Wanneer je de method forward() aanroept, moet je een parameter meegeven.
  • Wanneer je de method forward() aanroept, wordt in de terminal geprint hoeveel stappen de turtle zet.
  • Wanneer je de method left() aanroept, moet je een parameter meegeven.
  • Wanneer je de method left() aanroept, wordt in de terminal geprint hoeveel graden de turtle draait.

Projecttraject

  • De class Turtle
  • De method __init__(self)
  • Een class maken
  • self = instance
  • Opbouw van een class

De speciale parameter self

Een class method is vrijwel gelijk aan een normale functie, behalve dat een class method als eerste de parameter self verwacht. Aan deze parameter wordt de eigen instance van de class meegegeven wanneer je de method aanroept. Laten we kijken naar wat die instance van de class eigenlijk is. De instance van een class is de collectie van methods (en variabelen).

def calculate_squares_up_to(max_number):
    squares = []
    for number in range(max_number):
        squares.append(number ** 2)
    return squares

result = calculate_squares_up_to(5)
class Turtle:
    def __init__(self, shape):
        # transform turtle into shape

    def forward(self, distance):
        # move turtle by distance

    def left(self, angle):
        # turn turtle counterclockwise
        # by angle in degrees

master_oogway = Turtle("turtle") 

Als je de functie calculate_squares_up_to(max_number) aanroept met result = calculate_squares_up_to(5), dan komt hetgeen dat teruggegeven wordt, squares, in de variabele result terecht. Bij een class is er geen return-statement maar komt de hele inhoud van de class, dus alle methods (en variabelen), in de instance master_oogway terecht.

Gelukkig hoef je de instance niet steeds zelf mee te geven aan een method. Wanneer je een method aanroept wordt impliciet de instance als eerste parameter meegegeven. Maar waarom zou je die instance meegeven aan een method als je die aanroept? Omdat de instance alle methods en variabele bevat, kan je de informatie die daarin is opgeslagen in elke method gebruiken.

Stel je maakt een nieuwe method do_kungfu_move waarin je forward() en left() willen gebruiken:

class Turtle:
    def __init__(self, shape):
        # transform turtle into shape

    def forward(self, distance):
        # move turtle by distance

    def left(self, angle):
        # turn turtle counterclockwise
        # by angle in degrees

    def do_kungfu_move(self):
        # do kungfu move
        self.forward(130)
        self.left(350)
        self.forward(60)

Als je de method do_kungfu_move aanroept met master_oogway.do_kungfu_move(), dan geeft python automatisch de instance master_oogway mee aan de method. De parameter self is dus nu gelijk aan de instance master_oogway, daarmee doet self.forward(130) hetzelfde als master_oogway.forward(130).

self = instance

Het begrip self is een vaag begrip, maar je kunt met print-statements zien wat self is. Voeg aan fake_turtle.py de method do_kungfu_move() toe. En voeg aan de method ook een print-statement toe om de variabele self te printen. Gebruik hiervoor: print(f"{self=}"). Gebruik de instance master_oogway om de method do_kungfu_move aan te roepen. De variabele self is, als het goed is, gelijk aan de instance master_oogway. Om dit te controleren voeg je ook een print-statement toe om master_oogway te printen: print(f"{master_oogway=}").

Je hebt nu twee keer een object geprint. Dat ziet er misschien wat gek uit, maar je kunt er wel nuttige informatie uithalen. Aan het eind zie je getallen met letters staan, dat is het geheugenadres waar het object is opgeslagen. Als je het geheugenadres van de instance master_oogway vergelijkt met die van de variabele self dan zie je dat deze hetzelfde zijn. Dat betekent dat de objecten ook dezelfde objecten zijn, self is dus gelijk aan master_oogway!

self is de instance zelf. Met andere woorden: als je een andere instance gebruikt, dan verandert self mee. Om dit te testen maak je een instance toby aan, de tweede instance. Roep de method do_kungfu_move aan met toby. Je ziet dat het geheugenadres van de variabele self nu anders is dan de vorige self. Je controleert of het geheugenadres overeenkomt met het geheugenadres van de instance toby.

Pseudo-code

class Turtle:
    def __init__(self, shape):
        # transform turtle into shape
        ...

    def forward(self, distance):
        # move turtle by distance
        ...

    def left(self, angle):
        # turn turtle counterclockwise
        # by angle in degrees
        ...

    def do_kungfu_move(self):
        # do kungfu move
        self.forward(130)
        self.left(350)
        self.forward(60)
        # print self
        print(f"{self=}")
Testcode
fake_turtle.py
master_oogway = Turtle("turtle")
print(f"{master_oogway=}")
master_oogway.do_kungfu_move()

toby = Turtle("turlte")
print(f"{toby=}")
toby.do_kungfu_move()
(ECPC) > python fake_turtle.py

Checkpunten

  • Het geheugenadres van master_oogway is gelijk aan het geheugenadres van self.
  • master_oogway is niet gelijk aan self als de method met een andere instance wordt aangeroepen.

Projecttraject

  • De class Turtle
  • De method __init__(self)
  • Een class maken
  • self = instance
  • Opbouw van een class

Instance attribute

De instance van een class bevat niet alleen alle methods, maar kan ook variabelen bevatten. In het voorbeeld hieronder voegen we de variabele quote toe in de __init__-method en daarmee ook aan de instance. Dit wordt een instance attribute genoemd.

class Turtle:
    def __init__(self, shape):
        # transform turtle into shape
        self.quote = "Yesterday is history, Tomorrow is a mystery, but Today is a gift. That is why it is called the present"

    ...
De instance attribute quote is nu onderdeel van de instance. We kunnen die oproepen binnen elke method met self.quote maar ook buiten de class:

turtles.py
...
master_oogway = Turtle("turtle")

print(master_oogway.quote)
(ECPC) > python turtles.py

Opbouw van een class

Bekijk onderstaande code. Wat doet deze code precies? Verplaats daarna de onderdelen naar de juiste plek in de code. Twijfel je of je nog weet wat een module is kijk dan voor meer informatie in de paragraaf modules.

Projecttraject

  • De class Turtle
  • De method __init__(self)
  • Een class maken
  • self = instance
  • Opbouw van een class
Classes importeren

Wat is nu het praktisch nut van classes en methods gebruiken in plaats van functies? Want in plaats van

forward(master_oogway, distance=50)
heb je nu
master_oogway.forward(distance=50)
en dat is even lang. Het grote voordeel ontstaat pas wanneer de class ingewikkelder wordt en meer data gaat bewaren. Ook kun je de class in een ander pythonbestand (bijvoorbeeld animals.py) zetten en alle functionaliteit in één keer importeren met:
from animals import Turtle

master_oogway = Turtle()
...
Op deze manier kun je code ook makkelijker delen en verspreiden. Zodra je een class definieert zal Visual Studio Code tijdens het programmeren je code automatisch aanvullen. Zodra je typt master_oogway.f hoef je alleen maar op Tab te drukken en Visual Studio Code vult de rest aan.

Class Particle

Je hebt een class Particle gemaakt in een niew bestand particle.py. Als je een instance aanmaakt van de class Particle kun je de naam van het deeltje meegeven en de spin (bijvoorbeeld: 0.5). De instance attributes van deze class zijn 'name' en 'spin'. Er is ook een method is_up_or_down() om terug op te vragen wat de spin van het deeltje op dat moment is (spin omhoog/positief of spin omlaag/negatief). Door de method flip() op te roepen wordt de spin van het deeltje omgekeerd.
ECPC
├── oefenopdrachten
           ├── particle.py
           └── •••
└── pythondaq
           └── •••

Pseudo-code

# class Particle:
#   def __init__(self, name, spin):
#       make instance attribute from name
#       make instance attribute from spin
#
#   def is_up_or_down():
#       print up when spin is positive
#       print down when spin is negative
#       ...
#
#   def flip():
#       make spin positive if spin is negative
#       make spin negative if spin is positive
#       ...
Testcode
particle.py
proton = Particle('mooi proton', 0.5)
proton.is_up_or_down()
proton.flip()
proton.is_up_or_down()
print(f"{proton.spin=}")
print(f"{proton.name=}")
(ECPC) > python particle.py

Checkpunten

  • Naam en spin worden aan instance meegegeven.
  • Naam en spin zijn instance attributes en kunnen zowel binnen een method als buiten de class opgeroepen worden.
  • Method is_up_or_down() print 'up' als de spin positief is en 'down' als het negatief is.
  • Method flip() maakt de spin positief als de spin negatief is, en negatief als de spin positief is.

Projecttraject

  • Class Particle

Class ProjectileMotion

Je gaat een waterraket een aantal keer wegschieten met steeds een andere beginsnelheid en lanceerhoek. Je hebt een instance aangemaakt van de class ProjectileMotion. De beginsnelheid en de lanceerhoek bewaar je steeds met de method add_launch_parameters(). Om in een keer alle beginsnelheden op te vragen gebruik je de method get_initial_velocities(). Om alle lanceerhoeken op te vragen gebruik je de method get_launch_angles(). Op basis van de gegevens (en door de luchtweerstand te verwaarlozen) bepaal je de vluchtduur en het bereik van de raket. Je kunt de vluchtduur van alle vluchten opvragen met de method get_time_of_flights() en het bereik van alle vluchten met get_flight_ranges(). Zie het tabblad check voor de nodige vergelijkingen.
ECPC
├── oefenopdrachten
           └── •••
├── pythondaq
           └── •••
└── projectile-motion
           ├── water-rocket.py
           └── •••

Pseudo-code

# class ProjectileMotion
#    ...
#    __init__
#       ...
#    add_launch_parameters
#       ...
#    get_initial_velocities
#       ...
#    get_launch_angles
#       ...
#    get_time_of_flights
#       ...
#    get_flight_ranges
#       ...
Testcode
water_rocket.py
speedy = ProjectileMotion()
speedy.add_launch_parameters(v=28, angle=68)
speedy.add_launch_parameters(v=11, angle=15)

v = speedy.get_initial_velocities()
angles = speedy.get_launch_angles()
x = speedy.get_flight_ranges()
t = speedy.get_time_of_flights()

print(f"{v=}")
print(f"{angles=}")
print(f"{x=}")
print(f"{t=}")
(ECPC) > python water_rocket.py

Checkpunten

  • De code bevindt zich in een GitHub-repository .
  • De method add_launch_parameters verwacht een beginsnelheid in meter per seconde en een lanceerhoek in graden.
  • De method get_initial_velocities geeft een lijst terug met alle beginsnelheden van de ingevoerde parameters.
  • De method get_launch_angles geeft een lijst terug met alle lanceerhoeken van de ingevoerde parameters.
  • De time-of-flight wordt berekend met 2 * v_y / g.
  • De beginsnelheid in de y-richting wordt berekend met v_y = v * sin(lanceerhoek).
  • Het bereik wordt berekend met time_of_flight * v_x.
  • De beginsnelheid in de x-richting wordt berekend met v_x = v * cos(lanceerhoek).
  • De lanceerhoek wordt in radialen meegegeven aan de trigonometrische functies.
  • De method get_time_of_flights geeft een lijst terug met de vluchtduur in seconden corresponderend met de ingevoerde parameters.
  • De method get_flight_ranges geeft een lijst terug met het bereik in meters corresponderend met de ingevoerde parameters.

Projecttraject

  • Class ProjectileMotion

Raise exception

Het is niet logisch om een lanceerhoek boven een bepaalde waarde in te voeren of om een negatieve beginsnelheid mee te geven. Zorg dat in die gevallen een error afgegeven wordt. Meer informatie hierover vind je in de paragraaf Exceptions.

Subclass

Subclasses

Je kunt behalve een class ook een subclass aanmaken. De class Turtle heeft hele handige methods maar je kunt een specifiekere class GiantTortoise maken.

class GiantTortoise(Turtle):
    def __init__(self):
        super().__init__()
        self.shape("turtle")
        self.color("dark green")
        self.turtlesize(5)
        self.speed(1)

    def move(self, distance):
        steps = range(0, distance, 5)
        i = 1
        for step in steps:
            self.tiltangle(i * 5)
            self.forward(step)
            time.sleep(1)
            i = i * -1

Door de parentclass Turtle tussen ronde haakjes mee te geven aan de nieuwe subclass GiantTortoise krijgt de subclass alle functionaliteit mee van de parentclass, waaronder alle methods zoals forward(). Als je in de __init__()-method van de subclass methods of attributes wilt gebruiken van de parentclass, moet je ervoor zorgen dat de parentclass is geïnitialiseerd . Dit doe je met super().__init__(). Hierbij verwijst super() naar de parentclass en met __init__() voer je de __init__()-method van de parentclass uit. Nadat je in de __init__()-method van de subclass de eigenschappen van de reuzenschildpad hebt gedefinieerd, kun je extra functionaliteit gaan toevoegen, bijvoorbeeld de manier van bewegen met de method move().

super().__init__()

  1. Maak een bestand aan waarin je de subclass GiantTortoise aanmaakt.
  2. Zorg dat de volgende voorbeeldcode werkt:
    t = GiantTortoise()
    t.move(50)
    
  3. Wat gebeurt er als je super().__init__() weglaat?

Hawksbill turtle

  1. Maak een subclass aan voor de Hawksbill turtle.
  2. De Hawksbill turtle is een zeeschildpad. Maak de omgeving van de schildpad standaard blauw met self.screen.bgcolor("cyan").
  3. Schrijf een method swim() die de schildpad over het scherm laat bewegen.

  1. Wanneer je de Google Style Guide2 volgt schrijf je de naam van de class in CapWords of CamelCase. 

  2. Google. Google python style guide. URL: https://google.github.io/styleguide/pyguide.html