Werken met functies
Inleiding
Tot dusver heb je de code simpelweg in één bestand getypt en dat bestand direct uitgevoerd (vanuit Thonny). Voor kleine stukjes code werkt dit prima, maar zodra je uitgebreidere programma’s gaat schrijven wordt dit al snel onoverzichtelijk. Ook gebruik je dezelfde code vaak meermaals, die code op twee of meer plaatsen herhalen is inefficiënt en foutgevoelig.
In dit hoofdstuk leer je daarom eerst over functies. Een functie is een herbruikbaar stuk code, idealiter met één specifieke taak. Deze functie kun je vervolgens vaker aanroepen. Bijvoorbeeld:
namen = ["Ali", "Marie", "John", ]
# Definieer de functie `groet`
def groet(naam):
print(f"Hallo, {naam}!")
# Roep `groet` aan voor elke naam in `namen`
for naam in namen:
groet(naam=naam)
# Hallo, Ali!
# Hallo, Marie!
# Hallo, John!
Nu is dit een eenvoudig voorbeeld en is de winst die je behaalt ten opzichte van drie keer een print
-statement uitschrijven wellicht niet zo groot. Maar stel je voor dat het een complexe berekening betreft, en de lijst bestaat niet uit drie elementen maar uit 100… Dan ervaar je al snel de voordelen van een herbruikbare functie!
Functies gebruik je dus om herhaling in je code te voorkomen, maar ook om je code te structureren. Naast het gebruik van functies zijn er nog meer manieren om je code te structureren, zoals werken in verschillende bestanden en mappen. Ook dat leer je in dit hoofdstuk.
Leerdoelen
Aan het eind van dit hoofdstuk:
-
Begrijp je wat een functie is
-
Begrijp je hoe functies helpen om je code te structureren
-
Begrijp je wat argumenten in een functie zijn
-
Begrijp je het verschil tussen positionele en sleutelwoordargumenten
-
Begrijp je hoe je argumenten optioneel maakt
-
Begrijp je wat en wanneer een functie iets teruggeeft
-
Begrijp je hoe je code in bestanden en mappen structureert
-
Begrijp je wat een import is en hoe dit werkt
Functies: een inleiding
Een functie is een stuk code dat je eerst definieert om later (meermaals) te gebruiken. Een functie kan optioneel argumenten hebben en kan optioneel een waarde teruggeven met return. In de basis ziet een functie er als volgt uit:
def kwadraat(getal):
kwadraat = getal ** 2
return kwadraat
Op regel één zie je dat een functie definitie begint met def
(van define), gevolgd door een zelfgekozen naam. Na de naam volgen twee haakjes (()
) met daarin optioneel één of meer argumenten. In dit geval is er één argument met de naam getal
. De definitie sluit je af met een dubbele punt, waarna op regel twee het blok van de functie begint. Alle code in dit blok zal worden uitgevoerd als je de functie aanroept.
In de volgende paragrafen leer je meer over het werken met functies en hoe je ze gebruikt om je code te structureren.
Werken met functies
Nu je een basisbegrip hebt van functies, leer je in deze paragraaf hoe je met functies werkt.
Functies aanroepen
Bekijk nog eens de volgende eenvoudige functie:
def kwadraat(getal):
kwadraat = getal ** 2
return kwadraat
Als je het bestand met deze code uitvoert, zal er nog niets zichtbaars gebeuren. De functie is gedefinieerd, maar nog niet gebruikt. Daarvoor dien je de functie aan te roepen:
kwadraat(getal=10) # Roep aan met naam van argument
kwadraat(10) # Roep aan zonder naam van argument
Je ziet dat je de naam van het argument getal
ook weg kunt laten. Verderop leer je hier meer over.
Voer je het bestand nu uit, dan zie je nog steeds niets zichtbaars gebeuren in Thonny (in de shell overigens wel). De functie geeft het resultaat terug met return
, maar in dit geval wordt het resultaat nergens aan teruggegeven en 'verdwijnt' het.
Wijs je het resultaat toe aan een variabele, dan kun je er verder mee werken. Bijvoorbeeld het resultaat printen:
resultaat = kwadraat(getal=10)
print(resultaat) # 100
Je kunt niet alleen het resultaat van een functie aan een variabele toewijzen, maar ook de functie zelf. Vervolgens kun je dan die variabele aanroepen:
Omdat dit werkt, kun je een functie ook als argument meegeven aan een andere functie:
De functie
Als je de functie
Let er tot slot op dat je geen haakjes ( |
Argumenten in een functie
In deze paragraaf leer je meer over de argumenten in een functie.
Positionele argumenten en sleutelwoordargumenten
Eerder zag je al dat je één of meer argumenten kunt opgeven bij het definiëren van een functie. Deze argumenten dien je vervolgens ook op te geven als je de functie aanroept.
Het is aan de code die de functie aanroept of je de argumenten expliciet noemt of niet, onderstaande levert hetzelfde op:
def groet(naam):
print(f"Hallo, {naam}")
groet("John") # Hallo, Johnn
groet(naam="John") # Hallo, Johnn
Stel dat je de functie aanpast naar het groeten met voor- en achternaam:
def groet(voornaam, achternaam):
print(f"Hallo, {voornaam} {achternaam}")
Wil je de functie nu aanroepen zonder de argumenten te benoemen, dan is het belangrijk de volgorde van de definitie aan te houden. Python zal namelijk de eerst opgegeven waarde koppelen aan het eerste argument, de tweede waarde aan het tweede argument, enzovoorts.
groet("John", "Doe") # Hallo, John Doe
groet("Doe", "John") # Hallo, Doe John
Roep je de functie op deze wijze aan, dan spreek je van positionele argumenten (omdat de positie uitmaakt).
Je kunt de functie ook aanroepen mét het benoemen van de argumenten. De volgorde maakt in dat geval niet meer uit:
groet(voornaam="John", achternaam="Doe") # Hallo, John Doe
groet(achternaam="Doe", voornaam="John") # Hallo, John Doe
Doordat je de argumenten expliciet koppelt aan de naam, maakt de volgorde niet uit, Python weet nu wat je bedoelt. Dit noem je sleutelwoordargumenten (keyword argument).
Je kunt positionele argumenten en sleutelwoordargumenten ook door elkaar gebruiken. Wel is het belangrijk dat sleutelwoordargumenten altijd na de positionele argumenten komen.
groet("John", achternaam="Doe") # Hallo, John Doe
groet("Doe", voornaam="John") # Werkt niet
In het laatste geval zul je een foutmelding krijgen dat voornaam
tweemaal is opgegeven. De eerste waarde wordt automatisch aan het eerste argument (voornaam
) gekoppeld. Nogmaals voornaam
opgeven is dan niet mogelijk.
Optionele argumenten
In bovenstaande groet
-functie zijn de twee argumenten voornaam
en achternaam
verplicht. Als je de functie aanroept met bijvoorbeeld alleen de voornaam
, krijg je een foutmelding.
Er zijn situaties denkbaar waarbij je één of meer argumenten optioneel wil maken. Dit doe je door het een standaardwaarde toe te kennen bij het definiëren. Dit doe je door het argument te laten volgen door een is-teken (=
), gevolgd door de standaard waarde.
def groet(naam, enthousiasme_niveau=1):
uitroeptekens = "!" * enthousiasme_niveau
print(f"Hallo, {naam}{uitroeptekens}")
groet("John") # Hallo, John!
groet("John", enthousiasme_niveau=1) # Hallo, John!
groet("John", enthousiasme_niveau=2) # Hallo, John!!
groet("John", enthousiasme_niveau=0) # Hallo, John
Zoals je ziet kun je de functie nu aanroepen met alleen een naam. In dat geval wordt er standaard één uitroepteken geplaatst. Je kunt dit nog steeds expliciet maken als je wil, door alsnog enthousiasme_niveau=1
mee te geven. Je kunt ook andere waarden toekennen.
Een ander veelgebruikte mogelijkheid is None
als standaardwaarde mee te geven, en hier in het functieblok op te controleren:
def groet(voornaam, achternaam=None):
volledige_naam = voornaam
if achternaam:
volledige_naam += " " + achternaam
print(f"Hallo, {volledige_naam}.")
groet("John") # Hallo, John.
groet("John", "Doe") # Hallo, John Doe.
De term parameter verwijst naar de namen bij het definiëren van de functie. De term argument verwijst naar het object dat je meegeeft aan de functie. In deze en andere hoofdstukken lees je in beide gevallen de term argument.
|
Teruggeven van waarden uit een functie
In de meeste voorbeelden zag je dat de functie iets print. In de praktijk zul je een functie hiervoor niet vaak gebruiken, maar wil je dat de functie iets doet, en dan het resultaat teruggeeft. Dit teruggeven doe je met het sleutelwoord return
.
def kwadraat(getal):
kwadraat = getal ** 2
return kwadraat
resultaat = kwadraat(getal=10)
print(resultaat) # 100
Meerdere returns
Het teruggeven hoeft niet per se aan het einde van de functie, het kan bijvoorbeeld afhankelijk zijn van een if-else
statement dat je terug geeft. Alle code na een uitgevoerde return
wordt niet uitgevoerd.
def controleer_even_getal(getal):
if getal % 2 != 0:
return "Het opgegeven nummer is oneven."
return "Het opgegeven nummer is even."
print(controleer_even_getal(7)) # Het opgegeven nummer is oneven.
print(controleer_even_getal(4)) # Het opgegeven nummer is even.
Als het getal oneven is, dan wordt de laatste return
nooit bereikt.
In dit voorbeeld zie je de modulo-operator ( |
None
In de voorbeelden waar alleen een print
in de functie voorkwam, lijkt het alsof de functie niets teruggeeft. Het is echter zo dat Python aan het einde van de functie impliciet None
teruggeeft wanneer je geen expliciete return
opgeeft. Je kunt dit eenvoudig controleren:
def groet(naam):
print(f"Hallo, {naam}")
# Geen return
resultaat = groet(naam="John")
print(resultaat) # None
Je mag ook zelf return None
toevoegen, of korter: alleen return
. Dit kun je gebruiken als je met bijvoorbeeld een if-else
statement de functie vroegtijdig wil verlaten zonder waarde:
def controleer_even_getal(getal):
if getal % 2 != 0:
return
return "Het opgegeven nummer is even."
In dit geval geeft de functie None
terug als het getal oneven is.
Je code structureren met functies
Een functie kun je zien als een bouwsteen van je code. Met meerdere functies bouw je zo aan een uitgebreider programma. Het is raadzaam om je functies één verantwoordelijkheid te geven, dat houdt je code overzichtelijk en begrijpelijk. Geef je functienamen ook duidelijke, beschrijvende namen.
Net als bij de namen van variabelen zijn er een aantal regels voor en afspraken over functienamen. De regels zijn:
De stijl-afspraak is om enkel kleine letters en underscores te gebruiken voor functienamen (ook wel snake_case genoemd). Bijvoorbeeld: |
Als eenvoudig voorbeeld zie je hieronder een programma dat de BMI van de gebruiker uitrekent.
def verkrijg_gegevens():
"""
Vraag lengte en gewicht om de BMI te kunnen berekenen.
"""
lengte = input("Wat is je lengte (in cm) ")
gewicht = input("Wat is je gewicht (in kg)? ")
lengte = int(lengte)
gewicht = int(gewicht)
return lengte, gewicht
def bereken_bmi(lengte, gewicht):
"""
BMI: Gewicht in kg, gedeeld door het kwadraat van lichaamslengte in meters.
"""
return gewicht / (lengte/100)**2
def main(naam):
"""
Hoofdfunctie, bereken het BMI van de gebruiker.
"""
print(f"Welkom bij de BMI-calculator, {naam}.\n")
lengte, gewicht = verkrijg_gegevens()
bmi = bereken_bmi(gewicht=gewicht, lengte=lengte)
bmi = round(bmi, 2)
print(f"\n{naam}, je BMI is {bmi}.\n")
main("John") # Roep main aan
Je ziet dat er drie functies zijn gedefinieerd. De functie vraag_gegevens
handelt het uitvragen van de persoonsgegevens af. De functie bereken_bmi
handelt het berekenen van de BMI af. De laatste functie (main
) voegt tot slot alles samen.
Er zijn een aantal zaken die opvallen:
Uitpakken
In de functie verkrijg_gegevens
worden twee variabelen teruggegeven, gescheiden door een komma. In de functie main
worden deze variabelen uitgepakt. In Werken met tuples lees je nog eens over het uitpakken van tuples
.
Duidelijkheid
Elke functie heeft een duidelijke naam en een korte beschrijving van wat hij doet. Je hoeft enkel de functie main
te lezen om te begrijpen wat er gaande is, zonder de inhoud van de overige functies te lezen.
Volgorde
De volgorde van definiëren en aanroepen is relevant. In Python kun je pas iets aanroepen nadat het is gedefinieerd. Python leest elk bestand van boven naar beneden. Zou je main()
naar boven verplaatsen, dan krijg je een foutmelding dat verkrijg_gegevens
niet bestaat.
Je kunt de volgorde van de functies wel omdraaien. Pas op het moment dat je main()
uitvoert, worden ook de andere functies aangeroepen, en op dat moment zijn ze al gedefinieerd. Het maakt dan niet uit dat bijvoorbeeld bereken_bmi
eerder is gedefinieerd dan verkrijg_gegevens
. Als beide maar beschikbaar zijn op het moment dat main
wordt aangeroepen.
Je code verder structureren
Je hebt geleerd dat je functies kunt gebruiken om je code op te delen in behapbare stukjes code, die je ook kunt hergebruiken. Als je programma groter wordt, zul je merken dat het handig is om je functies te verdelen over verschillende bestanden en mappen zodat je overzicht houdt. Hoe je dat doet leer je in deze paragraaf.
Je code structureren met modules (bestanden)
De eerste stap in het structureren van je code is vaak het verdelen over meerdere bestanden. Dergelijke bestanden heten in Python modules. Hoe je de bestanden indeelt is aan jou en hangt af van de context van het programma. Dit kan bijvoorbeeld op onderwerp of, type functionaliteit zijn.
Tot dusver heb je steeds gewerkt in één .py
-bestand. Om de werking van modules te demonstreren maak je nu twee bestanden aan in dezelfde map: main.py
en rekenen.py
.
In Thonny kun je een nieuw bestand maken en opslaan, naast het bestand waar je al in werkt. Het nieuwe bestand opent in een nieuw tabblad, zodat je eenvoudig tussen beide bestanden kunt wisselen.
In rekenen.py
plaats je twee functies: kwadraat
en getal_is_even
.
def kwadraat(getal):
kwadraat = getal ** 2
return kwadraat
def getal_is_even(getal):
if getal % 2 != 0:
return False
return True
Als je deze functies in main.py
wil gebruiken, moet je ze importeren. Dit doe je met import
. Hoewel het niet verplicht is, is het de gewoonte om dit bovenaan het bestand te doen. Er zijn twee manieren om de functies te importeren. Met de eerste manier importeer je de gehele module, en dus alle inhoud ervan, in één keer.
# main.py
import rekenen
Je gebruikt import
met daarachter de naam van de module die je wil importeren. Je laat hierbij het achtervoegsel .py
weg.
Vervolgens kun je de functies en andere gegevens uit rekenen.py
gebruiken in main.py
:
# main.py
import rekenen # Importeer de gehele module `rekenen`
getal = 10
even_of_oneven = "oneven"
# Gebruik functie `kwadraat` uit module `rekenen`
kwadraat = rekenen.kwadraat(getal=getal)
# Gebruik functie `getal_is_even` uit module `rekenen`
is_even = rekenen.getal_is_even(getal=getal)
if is_even:
even_of_oneven = "even"
print(f"Het kwadraat van {getal} is {kwadraat}. {getal} is een {even_of_oneven} getal.")
Je hebt de module rekenen
als geheel geïmporteerd. Om hier nu iets uit te gebruiken is het nodig om de functienaam vooraf te laten gaan door rekenen
, gescheiden door een punt. Doe je dit niet, dan zul je een fout krijgen omdat de functie niet in de huidige module is gedefinieerd.
Je kunt de functies die je nodig hebt ook direct importeren:
# main.py
from rekenen import kwadraat # Importeer de functie `kwadraat` uit rekenen
# Gebruik functie `kwadraat` uit module `rekenen`.
# Deze is nu direct beschikbaar in `main`
kwadraat = kwadraat(getal=10)
print(kwadraat) # 100
In dit geval heb je alleen de functie kwadraat
nodig. Met from … import …
geef je aan wat je precies uit welke module wil importeren. In het huidige bestand kun je nu direct de naam van de functie gebruiken, zonder de module te noemen.
Wil je meerdere functies importeren, dan kan dat ook:
# main.py
from rekenen import kwadraat, getal_is_even
Je scheidt de te importeren items dan door een komma.
Je code structuren met packages (mappen)
Net als met het werken met bijvoorbeeld foto’s op je computer, kun je de modules ook nog verder organiseren door ze in mappen te plaatsen. Deze mappen noem je packages.
Niet elke map is een package, je dient er eerst een Python-bestand met de bestandsnaam __init__.py
in te plaatsen (aan beide kanten van init staan twee underscores). Dit is het bestand dat zal worden uitgevoerd als je de package importeert. Over het algemeen kun je het leeg laten. Maak in de map waar je in werkt, een nieuwe map functies
aan, met daarin drie Python-bestanden:
-
__init__.py
-
begroetingen.py
-
rekenen.py
(verplaats het voorgaande bestand naar deze map)
Plaats in begroetingen.py
een eenvoudige groet
functie.
def groet(naam):
print(f"Hallo, {naam}.")
In dezelfde map als waar je de map functies
hebt gemaakt, plaats je een bestand main.py
.
Importeren werkt vrijwel hetzelfde als eerder, maar nu moet je ook aan geven uit welke package je wil importeren.
# main.py
# Importeer de gehele module `begroetingen` uit package `functies`
from functies import begroetingen
# Gebruik de functie `groet` uit de module `begroetingen`
begroetingen.groet(naam="John")
# Hallo, John.
Om alle functies van begroetingen
te importeren (in dit geval is dat er maar één), gebruik je from … import …
. Je importeert nu begroetingen
in zijn geheel, en alles is dus beschikbaar. Wel moet je je groet
aan roepen met de module-naam begroetingen
eraan voorafgaand.
Je kunt ook weer specifieke functies importeren:
# main.py
# Importeer de functie `kwadraat` uit de module `rekenen`, welke zich in
# de package `functies` bevindt.
from functies.rekenen import kwadraat
# Gebruik functie `kwadraat` uit module `rekenen`, nu direct beschikbaar in `main`
kwadraat = kwadraat(getal=10)
print(kwadraat) # 100
Je ziet dat je weer de structuur from … import …
gebruikt. Maar nu geef je op uit welke module (rekenen) uit welke package (functies) je wil importeren door beide te benoemen, gescheiden door een punt (dotted path): functies.rekenen
.
Gebruik maken van de standaard bibliotheek
Naast dat je import
kunt gebruiken om je eigen code te importeren, kun je het ook gebruiken om modules die standaard al aanwezig zijn in Python te importeren.
Python heeft een uitgebreide standaard bibliotheek. Hierin is allerlei functionaliteit beschikbaar, zodat je die niet zelf hoeft te programmeren. Te denken valt aan mathematische bewerkingen, werken met bestanden, werken met websites, en nog veel meer.
Bekijk de lijst met ingebouwde functionaliteit op docs.python.org/3/library/
Importeren werkt exact hetzelfde als met je eigen modules en packages:
from math import pi, floor
# Bereken oppervlakte cirkel (πr²)
straal_in_cm = 5
oppervlakte = pi * straal_in_cm**2
# Afkappen op geheel getal
oppervlakte = floor(oppervlakte)
print(f"De oppervlakte van een cirkel met een "
f"straal van {straal_in_cm}cm "
f"is ongeveer: {oppervlakte}cm")
Importeren en uitvoeren
Als je een bestand uitvoert vanuit de shell of Thonny, dan zal Python alle code in het bestand uitvoeren. Zodra het een import
tegenkomt, zal het de import uitvoeren en de code in het geïmporteerde bestand ook uitvoeren.
Dit kan voor problemen zorgen als je een Python-bestand hebt dat je soms direct wil aanroepen, en soms wil gebruiken in een import. Neem het volgende voorbeeld, met twee bestanden bestand_1.py
en bestand_2.py
.
# bestand_1.py
import bestand_2
print(bestand_2.getal_is_even(10))
# 100
# True
# bestand_2.py
def kwadraat(getal):
kwadraat = getal ** 2
return kwadraat
def getal_is_even(getal):
if getal % 2 != 0:
return False
return True
print(kwadraat(10))
In bestand_1
importeer je bestand_2
en print je het resultaat van de functie getal_is_even(10)
. In bestand_2
heb je echter nog een functie gedefinieerd (kwadraat
) en voer je die uit op regel 21.
Voer je nu bestand_1
uit, dan zie je dat niet alleen True
wordt geprint, maar ook 100
, het resultaat van de code op regel 21 in de module bestand_2
. Dit is het gevolg van het feit dat alle code wordt uitgevoerd door Python, ook bij het importeren.
Als je wil dat regel 21 van module bestand_2
alleen wordt uitgevoerd als bestand_2
direct wordt uitgevoerd en niet als het wordt geïmporteerd, moet je te weten dat Python op de achtergrond aan elk bestand dat wordt uitgevoerd een speciale variabele __name__
koppelt. Als je bestand_2
importeert, zal __name__
de waarde bestand_2
krijgen. Maar als je bestand_2
direct uitvoert, zal het de naam __main__
krijgen, omdat het dan wordt beschouwd als het entreepunt van je programma.
De variabelen __name__ en __main__ hebben aan beide kanten van het woord twee underscores.
|
Dit gegeven kun je gebruiken om code alleen uit te voeren als een module direct wordt uitgevoerd. Bekijk de wijziging in bestand_2
:
# bestand_2.py
...
if __name__ == "__main__":
print(kwadraat(10))
Voer je nu bestand_1
uit, dan wordt bestand_2
geïmporteerd en krijgt __name__
de waarde bestand_2
. Omdat __name__
dus geen __main__
is, zal de functie niet worden uitgevoerd. Voer je bestand_2
direct uit, dan krijgt __name__
de waarde __main__
en zal de functie wel worden uitgevoerd.