Functies¶
Fun met Python functies!
Computing¶
Berekeningen, handelingen op data
Een computer rekent, en daarmee voert het handelingen op data uit. Met data heb je kennisgemaakt en hoe dit door de computer wordt opgeslagen (denk aan de “dozen” in het geheugen). Maar hoe worden bewerkingen op deze data uitgevoerd? We gaan kennismaken met een andere bouwsteen om te kunnen handelen: functies! Met een functie vertellen we de computer om een opdracht uit te gaan voeren.
Handelen¶
Input en output
len("huiswerk")
8
Je hebt inmiddels al functies gebruikt, bijvoorbeeld de functie len(x). Deze functie accepteert een parameter (een waarde, de input) en geeft een resultaat terug (output). We weten of zien niet welke handelingen len(x) verricht, we weten alleen dat het een waarde teruggeeft. Laten we Python functies eens vergelijken met wat je al kent van andere disciplines, bijvoorbeeld wiskunde.

De wiskunde kent namelijk ook functies. In dit voorbeeld is f de naam van de functie en de x de parameter. Stel we willen f(2) uitrekenen dan krijgen we f(2) = 2 * 2 + 3 De x in de berekening wordt dus vervangen met een 2. Het antwoord op f(2) is 7.
Structuur versus procedure¶
Definieert een structuur: wat het is (en wat logischerwijs volgt)
Dit is de syntax die wiskundigen hebben bedacht om een structuur te beschrijven en wat daar logischerwijs uit zou moeten volgen.
def f(x):
return 2 * x + 3
Definieert een procedure: wat het doet (en wat gedragsmatig volgt)
Python heeft ook functies. Het heeft de speciale syntax def (wat define betekent) om te zeggen “Ik definieer een functie”, in dit geval een functie met de naam f die een enkele parameter x accepteert. (book)
De combinatie van naam en welke parameters worden geaccepteerd wordt ook de signatuur van een functie genoemd, want dit is wat het uniek maakt en onderscheidt van andere functies die we gaan schrijven. Let ook op dat een functiedefinitie wordt afgesloten met de dubbele punt :!
Dit is al een stuk concreter, want anders dan de wiskundige beschrijving definiëren we hier een procedure (één of meerdere handelingen die uitgevoerd moeten worden op basis van een mogelijke input) en een resultaat dat mogelijk met het return statement wordt teruggegeven (output). Let ook op dat alles wat na de dubbele punt volgt moet worden ingesprongen om aan te geven dat het een codeblok is dat onderdeel van de functie is.
Syntax check! De dubbele punt geeft aan dat een nieuwe context volgt, je hebt dit bijvoorbeeld ook gezien bij conditionele statements als if:. De vuistregel is altijd inspringen na een dubbele punt!
Binnen een functie¶
def flipside(s):
"""Geeft s terug met de eerste en de tweede helft verwisseld"""
x = len(s) // 2
return s[x:] + s[:x]
flipside("automaat")
'maatauto'
Laten we gaan kijken naar de binnenkant van functies. We hebben hier een functie flipside gedefinieerd die de twee helften van een string verwisselt. Je ziet dat functie len(x), floor division // en string slicing wordt gebruikt. Maar je ziet ook andere dingen, bijvoorbeeld binnen de functie een nieuwe variabele x en tekst tussen driedubbele aanhalingstekens """.
Docstrings¶
def flipside(s):
"""Geeft s terug met de eerste en de tweede helft verwisseld"""
Met een string met driedubbele aanhalingstekens direct na de dubbele punt documenteer je de functie (voor jezelf en voor anderen). In deze docstring beschrijf je in één zin wat de functie doet. Voegt het iets toe, dan schrijf je op de regels daaronder ook op wat er binnenkomt, wat er teruggaat en welke aannames gelden. Sommige moderne tekstverwerkers gebruiken de docstrings om informatie over de functie te laten zien.
Gebruik variabelen¶
x = len(s) // 2
De lengte van de string is nodig om vervolgens met een floor division het aantal karakters tot het middelpunt van de string te bepalen. Dit aantal wordt aan de nieuwe variabele x toegekend en deze x wordt vervolgens twee keer gebruikt voor het slicen van de string (de start- en stopwaarden). We breken met het zetten van deze variabele x ook het probleem in stukjes op, en het scheelt ons typewerk!
Zonder die variabele zou de returnregel er zo uitzien:
return s[len(s) // 2 :] + s[: len(s) // 2]
Je had de stap van het zetten van een variabele dus kunnen overslaan en de start- en stopwaarde voor het slicen van de string ook als resultaat van len(s) // 2 kunnen schrijven, maar je merkt al dat het minder goed leesbaar is. Het gebruik van variabelen helpt de leesbaarheid en zelfs de computer vindt het prettiger omdat het efficiënter is: er hoeft maar één keer het aantal karakters tot het midden te worden berekend!
Welke naam hoort hier?
De naam x is hier eigenlijk slecht gekozen: hij zegt niet wat de waarde betekent. l (voor “lengte”) zou beter passen, maar die naam gebruiken we nooit, want de kleine letter l lijkt in veel lettertypen te veel op het cijfer 1.
Welke naam zou beter zijn dan x? Bedenk dat Python namen met meerdere letters ondersteunt, en dat de naam len al in gebruik is.
Variabelen opnieuw definiëren¶
days = seconds // (24 * 60 * 60) # aantal dagen
seconds = seconds % (24 * 60 * 60) # het restant
Gebruik variabelen en definieer ze opnieuw als het nodig is! In een functie die een aantal seconden omrekent naar dagen, uren, minuten en seconden wordt seconds steeds opnieuw gedefinieerd op basis van het resultaat van een vorige handeling. Een floor division (//) wordt eerst gebruikt om het aantal dagen te vinden, en met het restant (%) wordt op dezelfde manier het aantal uren gevonden. Hetzelfde proces herhaal je voor het aantal minuten, tot een restant aan seconden overblijft. In het practicum schrijf je die functie zelf.
Naast de docstring zie je hier ook commentaar: alles wat na een # volgt slaat Python over, het doet daar niets mee. Het is vooral voor mensen een manier om opmerkingen tussendoor te plaatsen.
Return versus print¶
Wat is het verschil?
Je hebt eerder kennisgemaakt met print(x) en deze functie geeft iets terug, althans zo lijkt het! return geeft een resultaat van een functie terug, wat is nu het verschil met print(x)?
def dbl(x):
"""Geeft het dubbele van x terug"""
return 2 * x
a_dbl = dbl(20) + 20
Dit is een eenvoudige functie die een waarde verdubbelt en het resultaat teruggeeft met return. Nu een variant met print(x) in plaats van return:
def dbl_pr(x):
"""Drukt het dubbele van x af"""
print(2 * x)
a_dbl_pr = dbl_pr(20) + 20
40
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[4], line 6
2 """Drukt het dubbele van x af"""
3 print(2 * x)
4
5
----> 6 a_dbl_pr = dbl_pr(20) + 20
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
Dit geeft een fout op regel 6, waar we bij het resultaat van de functie 20 willen optellen. Waarom deze fout terwijl we wél een resultaat op scherm zien (40)? De verklaring is dat een functie zonder return niets teruggeeft en dat “niets” (verrassend genoeg!) wél een waarde heeft, een waarde None (met type NoneType). None (“geen”) representeert het niets en Python zegt ons hier dat we 20 niet bij niets kunnen optellen!
Het niets is voor Python blijkbaar wel “iets” (None)!
Als je er bij stilstaat is dit niet zo gek. Wij mensen maken ook een onderscheid tussen iets en niets (en hebben daar woorden voor, iets of niets) en voor Python is dat niet anders: het heeft een manier nodig om niets te kunnen representeren en uit te drukken.
De foutmelding TypeError krijg je dus als je een bewerking of berekening wil uitvoeren die het datatype niet ondersteunt. Python is niet in staat om te rekenen met niets.
Het verschil¶
print wijzigt pixels op het scherm
return geeft resultaat van de functie-aanroep terug
return is de manier waarop een functie haar resultaat teruggeeft aan de code die haar aanroept. Zo kan de output van de ene functie de input van de volgende zijn.
Wat een functie hier niet doet
Elk voorbeeld in deze week rekent iets uit en geeft het terug; geen ervan verandert de waarde die je meegeeft. Dat blijft zo tot week 7. Daar leer je een lijst ter plekke te wijzigen, en dat heet mutatie.
Testen¶
assert flipside("huiswerk") == "werkhuis"
assert flipside("popster") == "sterpop"
assert flipside("az") == "za"
assert flipside("a") == "a"
assert flipside("") == ""
print(" toplap ~", flipside("laptop"))
toplap ~ toplap
Test jouw functies! Het is héél verleidelijk om een functie te schrijven en verder aan te nemen dat alles werkt, maar tot je hebt getest weet je niet zeker of alles in orde is. Kleine (of grote!) fouten zijn snel gemaakt.
Voeg daarom assert statements toe, waar assert een aanname betekent. Bijvoorbeeld, lees
assert flipside("huiswerk") == "werkhuis"
als “neem aan dat het resultaat van de aanroep flipside('huiswerk') gelijk is aan de string 'werkhuis'”. Als er geen fouten zijn, meldt Python niets. Test niet alleen het gewone geval maar ook een randgeval, zoals de lege string hierboven. Verder kan je natuurlijk altijd print statements gebruiken om waarden naar het scherm af te drukken.
Assertions
Het sleutelwoord assert staat voor een aanname. Je vraagt Python hier om aan te nemen dat de returnwaarde van flipside("huiswerk") gelijk is aan "werkhuis". Klopt die aanname niet, dan stopt Python het programma met een AssertionError en vertelt hij welke regel niet is geslaagd.
Wijzig een assertion in iets dat niet klopt en voer de cel uit. Dan zie je wat er gebeurt als een aanname faalt:
assert flipside("") == "ergfout"
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[6], line 1
----> 1 assert flipside("") == "ergfout"
AssertionError:
Dit is de foutmelding waar je vanaf nu op gaat letten. AssertionError betekent niet dat Python stuk is: het betekent dat jouw functie iets anders teruggeeft dan jij had aangenomen. Eén van de twee klopt niet, en het is aan jou om uit te zoeken welke.
None?¶
None is iets dat niets representeert, kan je dit ook testen?
assert dbl_pr(20) is None
40
Je ziet hier alleen 40 (vanwege het print statement, pixels op het scherm…) en verder niets, en dat is goed nieuws: de aanname dat de aanroep dbl_pr(20) inderdaad None teruggeeft klopt.
Belangrijke functies¶
rangesumchoice
Range¶
range geeft een iterator met integers terug. Het maakt niet uit als je niet precies weet wat een iterator is, het belangrijkst is dat je ziet dat list de iterator omzet naar een lijst met integers:
L = list(range(0, 100))
print(L)
output: [0, 1, 2, ..., 99]
Tot het eind!
Let op dat range, zoals bij vrijwel alles in Python, tot het eindpunt telt en niet tot en met.
Sum¶
sum telt een lijst van getallen op, en range maakt een lijst met integers (of eigenlijk een iterator met integers, maar daar kan sum ook mee overweg):
s = sum(range(3, 11))
print(s)
output: 52
L = list(range(3, 11))
print(sum(L))
output: 52
Een omslachtige manier om 40 + 2 uit te rekenen:
print(sum([40, 2]))
output: 42
Choice¶
Choice is een functie die een random keuze kan maken uit een gegeven lijst. Het is alleen niet standaard beschikbaar zoals de functies sum en range. Om de functie te kunnen gebruiken moeten we het eerst importeren.
from random import choice
L = list(range(0, 100))
rand = choice(L)
Imports¶
Random is wat een module wordt genoemd, soms wordt hier ook het woord library of functiebibliotheek voor gebruikt. Met het importeren van een module krijg je toegang tot alle functies die in deze module staan.
Optie 1:¶
import random
random.choice(list(range(0, 10)))
random.randint(0, 9)
random.uniform(0, 9)
In deze optie wordt de hele module toegevoegd. Om de functies te gebruiken die in deze module staan, noteren we eerst uit welke module ze komen en dan volgt de functienaam. Zo ontstaan er geen problemen als er meerdere modules worden geïmporteerd en er functienamen overeenkomen.
Optie 2:¶
from random import choice
from random import randint
from random import uniform
choice(list(range(0, 10)))
randint(0, 9)
uniform(0, 9)
from random import choice importeert een hele specifieke functie uit de module en niet de gehele module. Scheelt in de grootte van het programma. Is vooral handig om te gebruiken als je maar een enkele functie uit de module wilt gebruiken.
Optie 3:¶
from random import *
choice(list(range(0, 10)))
randint(0, 9)
uniform(0, 9)
from random import * importeert alle functies uit de module. Het is belangrijk om te weten welke functies dit zijn om te voorkomen dat er twee dezelfde functies in het programma staan. Bijvoorbeeld van een andere module of in je eigen code.
Handige modules¶
math
cos()sin()tan()sqrt()pi
random
choice()randint()uniform()
time
time()ctime()sleep()
Opdracht 1¶
Schrijf een functie maximum() die twee integers als parameters krijgt en de grootste van de twee teruggeeft. Voeg een docstring toe en test de functie met minstens twee assertions, waarvan er één een randgeval dekt (bijvoorbeeld twee gelijke getallen).
Opdracht 2¶
Schrijf een functie minutes() die een integer als parameter accepteert die een aantal seconden voorstelt, en teruggeeft hoeveel hele minuten dat zijn. Voeg een docstring toe en test de functie met minstens twee assertions, waarvan er één een randgeval dekt (bijvoorbeeld nul seconden).
Opdracht 3¶
Schrijf een functie alphabetic() die twee strings als parameters krijgt en de string teruggeeft die alfabetisch het laatst komt. Voeg een docstring toe en test de functie met minstens twee assertions, waarvan er één een randgeval dekt (bijvoorbeeld twee gelijke strings).