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:
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:
__init__(self)
Stel dat de init-method geen extra parameters mee krijgt, zoals in het volgende geval:
hoe maak je dan een instance aan van de class?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
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
Checkpunten:
- De class
Turtle
wordt geïmporteerd uit de moduleturtle
. - De instance is van de class
Turtle
met hoofdletter T. - Om de schildpad te laten bewegen roep je de method
forward()
ofleft()
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).
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"
...
quote
is nu onderdeel van de instance. We kunnen die oproepen binnen elke method met self.quote
maar ook buiten de class:
(ecpc) > python turtles.py
"Yesterday is history, Tomorrow is a mystery, but Today is a gift. That is why it is called the present"
Opbouw van een class
- Beschouw de onderstaande code
- 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
hebben we nu 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 (bijvoorbeeldanimals.py
) zetten en alle functionaliteit in één keer importeren met:
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
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
...
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
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
...
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
v=[28, 11]
angles=[68, 15]
x=[55.51602063607072, 6.167176350662587]
t=[5.292792645845066, 0.5804300705663054]
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__()
- Maak een bestand aan waarin je de subclass
GiantTortoise
aanmaakt. - Zorg dat de volgende voorbeeldcode werkt:
- Wat gebeurd er als je
super().__init__()
weglaat?
Hawksbill turtle
- Maak een subclass aan voor de Hawksbill turtle.
- De Hawksbill turtle is een zeeschildpad. Maak de omgeving van de schildpad standaard blauw met
self.screen.bgcolor("cyan")
. - Schrijf een method
swim()
die de schildpad over het scherm laat bewegen.
-
Wanneer je de Google Style Guide2 volgt schrijf je de naam van de class in CapWords of CamelCase. ↩
-
Google. Google python style guide. URL: https://google.github.io/styleguide/pyguide.html. ↩