Skip to content

Classes

Voor een snelle meting is het script dat je geschreven hebt bij opdracht quick 'n dirty meting 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.

Aanmaken van een class

Een class is een verzameling functies. Hieronder staat 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 een collectie van functies — de zogeheten methods van de class. De eerste method __init__() is speciaal (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. Later meer over de speciale parameter self, eerst gaan we kijken hoe je een class gebruikt.

Aanroepen van een class

Het aanroepen van een class lijkt veel op het aanroepen 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") 

Stel we hebben de functie calculate_squares_up_to(max_number). Dan roep je die aan met result = calculate_squares_up_to(5). Hierbij is calculate_squares_up_to(5) de naam van de functie en result de variabele waar de uitkomst heen gaat. 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 methods (en variabelen). De variabele master_oogway noemen we een instance van de class Turtle. Net zoals je een functie vaker kunt aanroepen, kan je ook meerdere instances van een class aanmaken. 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.

Meerdere instances

Je kunt meerdere instances hebben van dezelfde class, bijvoorbeeld voor verschillende schildpadden:

class Turtle:
    ...

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

__init__(self)

Stel dat de init-method geen extra parameters mee krijgt, zoals in het volgende geval:

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

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

    def left(self, angle):
        # turn turtle counterclockwise
        # by angle in degrees
hoe maak je dan een instance aan van de class?

Uitwerkingen
master_oogway = Turtle()

Omdat de instance master_oogway alle methods bevat kunnen we deze methods aanroepen:

master_oogway = Turtle("turtle")

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

turtle

Je bent inmiddels nieuwsgierig geworden naar de schildpad. De class Turtle zit standaard in Python, daarom kan je die importeren met from turtle import Turtle. Maak een bestand turtles.py waarin je een schildpad met de instancenaam master_oogway laat lopen en draaien.
ECPC
├── oefenopdrachten
           ├── turtles.py
           └── •••
└── pythondaq
           └── •••

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

# move turtle forward with 50 steps
...
# turn turtle left with 30 degrees
...

Checkpunten:

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

Projecttraject:

  • turtle

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 we de functie calculate_squares_up_to(max_number) aanroepen met result = calculate_squares_up_to(5), dan komt hetgeen we teruggeven, squares, in de variabele result terecht. Bij een class is er geen return-statement maar komt de hele inhoud van de class 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 we maken een nieuwe method do_kungfu_move waarin we 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 we de method do_kungfu_move aanroepen met master_oogway.do_kungfu_move() 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).

Instance attribute

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

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

  1. Beschouw de onderstaande code
  2. Bespreek met elkaar wat de code precies doet en verplaast 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.

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)
hebben we 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 VS 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(proton.spin)
print(proton.name)
(ecpc) > python particle.py

Checkpunten:

  • Naam en spin toestand worden aan instance meegegeven.
  • 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().
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 beginsnelheden van alle 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: v_y = v * sin(lanceerhoek).
  • Het bereik wordt berekend met time_of_flight * v_x.
  • De beginsnelheid in de x-richting: v_x = v * cos(lanceerhoek).
  • De lanceerhoek wordt in radialen meegegeven aan de trigonomische 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 die correspondeerd met de ingevoerde parameters.

Projecttraject:

  • Class ProjectileMotion
ProjectileMotion raise exception

Het is niet logisch als de lanceerhoek boven een bepaalde hoek uitkomt of als een negatieve beginsnelheid wordt ingevoerd. 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 we in de init-method van de subclass de eigenschappen van de Reuzenschildpad hebben gedefinieerd, kunnen we 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 gebeurd 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