TOPIC 3: Functies¶
In het vorige topic hebben we o.a. geleerd hoe we reeds bestaande functies kunnen oproepen in Python. De syntaxregels schrijven voor dat je de argumenten tussen haakjes moet zetten en dient te scheiden door komma’s.
De makers van Python konden wél voorzien dat we soms dingen als abs
nodig hebben. Maar ze konden bijvoorbeeld niet voorzien dat wij nu
toevallig ooit eens de functie \(f(x) = 10x^2-17x+8\) nodig hebben. Daarom
laat Python toe dat we zélf functies maken. In dit topic leren we hoe we
het arsenaal ingebouwde functies in Python kunnen uitbreiden met
zelfgemaakte functies. De functies uit math
en matplotlib
die we
moesten importeren in het vorige topic zijn eigenlijk ook “zelf”
gemaakte functies. De auteurs van math
hebben deze zelfgemaakte
functies in modules gestopt die bij iedere Python installatie standaard
zijn meegeleverd, en die wij dan hebben kunnen (her)gebruiken. Ook de
functies uit de matplotlib
module werden ooit door haar auteurs “zélf” gemaakt. In dit topic
leren we dus hoe ook wij zulke modules met zelfgemaakte functies kunnen
maken.
We beginnen met het definiëren van eenvoudige wiskundige functies zoals
de \(f\) van hierboven. Al gauw zullen we nood hebben aan meer
ingewikkelde functies die hun rekenwerk baseren op bepaalde beslissingen
die door de functie genomen moeten worden. Deze beslissingen worden door
Python genomen met behulp van het if
statement. De voorwaarde waarop
zo’n beslissing gebaseerd is kan waar of vals zijn, wat meteen een goede
motivatie is om het Booleaanse type uit te leggen (met waarden True
en
False
). We eindigen het topic met het programmeren van ingewikkeldere
functies die tupels als argumenten en resultaten opleveren.
Maar eerst dus gewone wiskundige functies.
Functies definiëren in Python¶
Inleiding¶
Beschouw volgende wiskundige functie \(f\):
Deze functie converteert een temperatuur gegeven in graden Fahrenheit naar het equivalent ervan in graden Celsius. Indien we de functie toepassen op \(80\) — genoteerd \(f(80)\) — krijgen we het resultaat \(26,66666...\).
We kunnen deze functie in Python als volgt definiëren en vervolgens oproepen:
def to_celsius(t):
return (t - 32.0) * 5.0 / 9.0
to_celsius(80)
26.666666666666668
Laat ons de eerste codecel even analyseren. Ze bevat een functiedefinitie. Iedere functiedefinitie
bestaat uit het woordje def
, gevolgd door de naam van de functie,
gevolgd door de parameters van de functie (in dit geval is dat slechts
één parameter t
), gevolgd door een dubbele punt, gevolgd door de
body van de functie. De body is wat we in de wiskunde het
functievoorschrift zouden noemen. Het bevat de Python code die uitrekent
wat er uit de functie dient te komen. In de body van deze functie zien
we slechts één statement staan: het return
statement. Het return
statement doet Python terugkeren naar de oproeper met de waarde van de
opgegeven expressie. In ons voorbeeld is to_celsius
de naam van de
functie.
In de er op volgende codecel zien we de oproep van onze versgebouwde functie
met 80 als argument. Een zélfgebouwde functie wordt dus syntactisch
helemaal op dezelfde manier opgeroepen als de ingebouwde functies uit
het vorige topic. Maar hier is wat er gebeurt bij de uitvoering: Python
“springt” bij de oproep van de functie naar de body. Hierbij worden de
parameters van de functie (in dit geval dus t
) tijdelijk geassocieerd
met de argumenten van de oproep (in dit geval 80
). Dan wordt de body
geëvalueerd. In ons geval staat daar een return
statement. Dat doet
Python de expressie evalueren en met de waarde “terugspringen” naar de
oproeper. In ons geval is dat de REPL. Deze zal de resulterende waarde
zoals gewoonlijk op het scherm printen.
Terminologie
Omtrent het oproepen van zelfgemaakte functies is er in de computerwetenschappen over de jaren heen (helaas) wat terminologische verwarring ontstaan. Ook wij zullen beide soms door elkaar gebruiken:
Soms spreekt men van parameter en argument. De parameter is de naam van het argument in de definitie van de functie. In ons voorbeeld is
t
de parameter en is80
het argument.Soms spreekt men echter van de formele parameter(s) en de actuele parameter(s). Dit is gewoon andere terminologie om hetzelfde aan te duiden. Indien we deze terminologie gebruiken, is
t
dus de formele parameter en is80
de actuele parameter.
Hieronder tonen we nog een voorbeeld van een zelfgemaakte functie. Deze functie heeft opnieuw één parameter en berekent het kwadraat van het meegegeven argument.
def square(x):
return x*x
square(4)
16
square(4.0)
16.0
square(3)+square(7)
58
square(square(3))
81
Interessant in deze experimenten is de lijn square(square(3))
. Deze
zal onze functie oproepen met 3
. Dat zal uiteraard \(9\) opleveren. Maar
vermits daar nog eens een oproep van square
omheen staat, zal de
functie nogmaals worden opgeroepen met \(9\). Het resultaat is daarom
\(81\).
Het volgende voorbeeld toont dat we ook functies van meerdere parameters
kunnen maken. average
neemt twee argumenten (genaamd x
en y
) en
berekent er het rekenkundig gemiddelde van. Bij zulke functies van
meerdere argumenten dienen zowel de parameters (bij de definitie) als de
argumenten (bij de oproep) gescheiden te worden door komma’s.
def average(x,y):
return (x + y) / 2.0
average(1,2)
1.5
In principe moet het aantal argumenten en het aantal parameters kloppen. Onderstaande experimenten laten zien wat gebeurt als je te veel of te weinig parameters meegeeft. We zien een beetje verderop dat je wel optionele parameters kan introduceren.
average(1,2,3)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-51c4f85249a8> in <module>
----> 1 average(1,2,3)
TypeError: average() takes 2 positional arguments but 3 were given
average(1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-0c1aa5f6a438> in <module>
----> 1 average(1)
TypeError: average() missing 1 required positional argument: 'y'
Merk tenslotte op dat return
expliciet geschreven dient te worden
teneinde met een betekenisvolle waarde uit een functie terug te keren.
Hetvolgende laat zien wat er gebeurt als we return
vergeten.
def square_met_return_vergeten(x):
x*x
square_met_return_vergeten(4)
We zien dus dat de functie netjes wordt opgeroepen (er komt immers geen
foutmelding) en dat de body dus wordt uitgevoerd. Op het
einde van de body keert Python gewoon terug naar de oproeper (in dit
geval de REPL). Op dit moment lijkt het alsof er geen waarde werd
teruggegeven. Dat is echter niet helemaal correct. Bij het ontbreken van
een return
statement zal Python -op het einde van een functie-body- automatisch met de waarde None
terugkeren naar de oproeper. Het
speciale aan None
is dat de printfase er niets mee doet. Maar het is
wel degelijk een waarde als een andere. Het is de enige waarde van het
type NoneType
dat speciaal is de taal is voorzien om met “niets” om te gaan. Meer hierover later.
Parameters zijn Lokaal¶
Het volgende experiment illustreert een heel belangrijke eigenschap van
Python functies, namelijk dat parameters lokaal aan de functie zijn.
Hiermee bedoelen we dat de namen van de parameters enkel gebruikt mogen
worden binnenin de body van de functie en niet daarbuiten. Dat zien we
in het volgende experiment. square
oproepen werkt perfect, daarbinnen is de x dus gekend. Maar gewoon proberen x
te evalueren.levert een foutmelding op aangezien. x
is in de REPL niet
gedefinieerd. De parameter x
is dus lokaal aan de functie square
.
def square(x):
return x * x
square(3)
9
x
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-16-6fcf9dfbd479> in <module>
----> 1 x
NameError: name 'x' is not defined
Het volgende stukje code toont een tweede belangrijke eigenschap van
Python functies en dat is namelijk dat de precieze naam van een
parameter niet terzake doet voor de gebruiker (t.t.z. de oproeper). Stel dat square
zou gedefiniëerd zijn met een parameter y
en niet met een x
zoals hiervoor. Voor de oproeper verandert er niets.
De keuze van de namen van parameters is dus enkel
de zaak van degene die de functie schrijft, niet van de oproeper.
def square(y):
return y * y
square(3)
9
Uiteraard dienen we consistent te zijn in de definitie van de parameters
en het gebruik ervan binnenin de body van de functie. Indien we kiezen
voor y
als parameter maar we gebruiken vervolgens x
in de body, dan
krijgen we een foutmelding tijdens de uitvoer van de body: Python klaagt dat x
niet gedefinieerd is in die functie.
def square(y):
return x * x
square(3)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-20-2cfd8bba3a88> in <module>
----> 1 square(3)
<ipython-input-19-df3a164a104b> in square(y)
1 def square(y):
----> 2 return x * x
NameError: name 'x' is not defined
Merk op dat dit de eerste keer is dat we een foutmelding zien die uit 2
stukken bestaat. Dat komt omdat Python onthoudt telkens wanneer een
functie wordt opgeroepen. Indien je dus een functie oproept die op haar
beurt een functie oproept die dan een fout oplevert, zal je een
foutmelding uit 3 stukken zien. Door de foutmelding correct te lezen kan
je dus te weten komen waar het is misgelopen. In bovenstaande
foutmelding zegt Python dat de fout ontstond in de naamloze “<module>
”
(t.t.z. de REPL). In het tweede stuk wordt er gezegd dat de fout (meer
precies dus) ontstond in de functie square
.
Naamgeving¶
In tegenstelling tot wiskundigen geven computerwetenschappers meestal
een goed gekozen naam aan hun functies en variabelen. De bedoeling
hiervan is dat je later nog wijs geraakt uit je eigen code. Dat geeft
dan aanleiding tot een stijl van programmeren die heel persoonlijk is.
Zo is onderstaande functie gemiddelde
perfect geldige Python en is het
technisch-wetenschappelijk volledig equivalent met bovenstaande
definitie van average
.
def gemiddelde(eerste_getal, een_zeer_rare_naam2):
return (eerste_getal + een_zeer_rare_naam2) / 2.0
gemiddelde(1,2)
1.5
Belangrijk is dat je “goede” namen kiest zodat je later je eigen code
nog kan lezen. Kies dus liever average
, celsius
of final_result
i.p.v. weinig zeggende namen zoals x1
, x2
of blah
. Verder raden we
aan van zoveel mogelijk consistent te zijn in je keuze. Kies dus niet
max_val
in één functie en maxVal
of maxval
of maximum_val
in een
andere. Inconsistente naamgeving maakt code doorgaans moeilijk leesbaar en verhoogt het aantal vergissingen bij het progranmmeren.
Maar nogmaals, over smaak en kleur valt niet te twisten. Dit geldt zowel
voor variabelen, functies als parameters van functies.
Procedurele Abstractie¶
Hieronder zien we de functie sum_of_squares
die de som van de kwadraten van twee getallen x
en y
berekent.
def sum_of_squares(x,y):
return x**2 + y**2
sum_of_squares(2,3)
13
Alhoewel deze functie helemaal correct is, zullen de meeste
computerwetenschappers echter volgende variante van sum_of_squares
verkiezen. Door de idee “kwadrateren” in een aparte Python functie te
stoppen wordt onze code plots veel meer leesbaar.
def square(x):
return x**2
def sum_of_squares(x,y):
return square(x)+square(y)
sum_of_squares(2,3)
13
We stoppen dus zoveel mogelijk “logisch samenhorende berekeningen” in
één functie die we vervolgens een goed gekozen naam geven. Dit heet
procedurele abstractie. Door een expressie als functievoorschrift in
een functie met een goede naam te stoppen abstraheren we de complexiteit
van die expressie “weg” omdat we er vanaf dan abstract over kunnen
nadenken. Indien we bijvoorbeeld een gegeven meetwaarde in graden
Fahrenheit wensen te converteren dienen we gewoon to_celsius
op te
roepen zonder dat we ons zorgen dienen te maken over hoe dat converteren
nu weeral precies in zijn werk ging.
Procedurele abstractie heeft als grote voordeel dat we complexiteit wegstoppen achter betekenisvolle namen en dat onze Python code hierdoor veel leesbaarder wordt. Procedurele abstractie heeft nog een tweede belangrijk voordeel. Wanneer we bijvoorbeeld een gegeven expressie twee of meerdere keren nodig hebben, kunnen we deze expressie beter wegstoppen in een functie. We dienen de expressie dan maar één keer te schrijven! Dat heeft als belangrijkste voordeel dat we latere veranderingen (bijvoorbeeld bij het ontdekken van een vergissing) slechts één keer hoeven te doen en dat alle code die de functie oproept hiermee automatisch aangepast is.
Lokale Variabelen & Blocks¶
Tot nu toe bestonden de body’s van onze functies slechts uit één enkel
return
statement. Dat is meestal echter niet het geval. In het
algemene geval bestaat de body van een functie uit een block. Een
block bestaat uit verschillende statements die door Python één na één
worden uitgevoerd. We kunnen een block herkennen aan het feit dat alle
statements van het block op hetzelfde niveau (van “de kantlijn”) worden
ingesprongen. Hieronder zien we een voorbeeld.
def polynomial(a, b, c, x):
first = a * x * x
second = b * x
third = c
return first + second + third
polynomial(2, 3, 4, 0.5)
6.0
polynomial(2, 3, 4, 1.3)
11.280000000000001
De functie polynonial
verwacht 4 argumenten. Haar body is een block
dat uit 4 statements[^2] bestaat: 3 assignment statements en daarna nog
een return statement. In het algemeen worden de statements uit een block
één na één, van boven naar onder, uitgevoerd. Python gaat dus bij het
oproepen van de functie eerst de variabele first
definiëren, dan de
variabele second
, daarna de variabele third
en zal ten slotte de
return
uitvoeren (en dus met de som van de drie eerste naar de
oproeper terugkeren).
Het is uiterst belangrijk te begrijpen dat de toekenningen die binnenin een functie worden uitgevoerd aanleiding geven tot variabelen die lokaal zijn voor die functie. Deze lokale variabelen bestaan dus enkel tijdens de uitvoering van de body van de functie en worden na de oproep opgeruimd door Python. Dus net zoals parameters lokaal zijn aan een functie zijn de variabelen die worden geïntroduceerd binnen een functie lokaal aaan de functie. Dat zien we als we onderstaande code meteen na de voorgaande code uitvoeren:
a
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-30-3f786850e387> in <module>
----> 1 a
NameError: name 'a' is not defined
first
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-31-271ac93c44ac> in <module>
----> 1 first
NameError: name 'first' is not defined
Algemeen ziet de syntax voor het definiëren van functies er dus als volgt uit.
Bij de oproep van de functie worden alle statements van het blok één na
één uitgevoerd. Indien één van die statements het return
statement is,
wordt er meteen uit de functie gesprongen met de bijhorende waarde.
Indien geen enkel statement uit het block return
is, “valt” de functie
terug naar de oproeper met de waarde None
.
Optionele Parameters¶
In onderstaande code is t
een functie die 2 parameters heeft waarvan er één optioneel is
(namelijk g
). Dat zien we aan het feit dat de parameter g
reeds in
de definitie van de functie een waarde krijgt (in dit geval \(9.81\)).
Men noemt dit de default waarde van g
. Optionele parameters kunnen
weggelaten worden bij de oproep. In dat geval wordt de default waarde
gebruikt.
from math import sqrt
def t(h, g = 9.81):
return sqrt(2*h / g)
t(1)
0.4515236409857309
Indien we echter bij de oproep alsnog een tweede argument meegeven dan wordt déze waarde gebruikt en wordt de default waarde
genegeerd. Dit wordt aangetoond in volgend experiment. Deze oproep maakt wél gebruik van de
mogelijkheid om een tweede argument mee te geven (in dit geval 1.63
).
t(1, 1.63)
1.1076975512434226
De oproep hieronder laat tenslotte nog een nieuwigheid zien. Tot nu toe
hebben we bij de oproep van functies de naam van de parameter niet
vermeld. Dat is immers een interne zaak van die functie! De derde oproep
laat zien dat we toch bij de oproep ook de naam van de parameter mogen
vermelden (hier: g
). Eigenlijk is dit een slechte gewoonte. Het zorgt
er namelijk voor dat degene die de functie ooit schreef nooit van
gedacht mag veranderen over de naam van zijn parameters. Immers, alle
oproepers die die naam expliciet vermelden zullen dan niet meer werken.
t(1, g = 1.63)
1.1076975512434226
Toch wordt deze gewoonte zeer veel gebruikt in Python code. Vooral bij functies die veel verschillende opties hebben kan je dit tegenkomen zoals bv. de plot
functie uit Matplotlib.
Stel dat je een functie hebt met 12 argumenten. Ofwel moet je als programmeur de volgorde van de 12 argumenten onthouden, ofwel gebruikt je de naam van de parameter expliciet. Het laatste kan soms makkelijker zijn vooral als er goede parameternamen gekozen zijn.
Booleaanse Waarden¶
De functies die we tot nu toe hebben gemaakt zijn tamelijk saai. Het
zijn eigenlijk allemaal gewoon wiskundige “formules” die in één return
statement zijn ondergebracht of onder te brengen zijn. We gaan nu ons
arsenaal aan Python statements en expressies uitbreiden om
interessantere functies te kunnen schrijven. Eén van die statements is
het if
statement. Maar alvorens we dat kunnen uitleggen hebben we nog
snel een nieuw type nodig: bool
.
Waarheidswaarden¶
Het type bool
werd vernoemd naar de wiskundige George Boole. Het kent
slechts 2 waarden: True
en False
. Het is belangrijk deze met een
hoofdletter te schrijven. Anders denkt Python dat we de naam van een
(ongedefinieerde) variabele gebruiken. Net zoals getallen evalueren de Booleaanse waarden naar zichzelf.
True
True
true
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-37-724ba28f4a9a> in <module>
----> 1 true
NameError: name 'true' is not defined
Booleaanse Operatoren¶
Net zoals voor int
en float
, bestaan er voor bool
een aantal
ingebouwde operatoren. Deze zogenoemde booleaanse operatoren
implementeren de beroemde waarheidstabellen uit de logica. Python kent
de operatoren not
, and
en or
. Toepassen van de operator not
op
een booleaanse waarde levert de andere booleaanse waarde op:
not True
False
not False
True
Toepassen van de operator and
op twee booleaanse waarden geeft True
als en slechts als beide operanden ook True
waren:
True and True
True
True and False
False
False and True
False
False and False
False
Toepassen van de operator or
op twee booleaanse waarden ten slotte,
geeft True
van zodra één van beide operanden True
is:
True or True
True
True or False
True
False or True
True
False or False
False
Voorrangsregels¶
Net zoals er voorrangsregels bestaan tussen +
en *
, zijn ook and
en or
aan zulke voorrangsregels onderworpen. Uit hetvolgende
experiment kan je makkelijk afleiden welke operator voorrang heeft. Doe
het!
windy = False
cold = False
sunny = True
windy and cold or sunny
True
Relationele Operatoren¶
Alhoewel bool
op zichzelf als verzameling best wel interessant is (men
spreekt over booleaanse algebra), komt het type toch pas goed tot zijn
recht indien we het in verband brengen met waarden van andere types. Dat
doen de zogenoemde relationele operatoren. Dat zijn operatoren die een
relatie tussen twee waarden aftoetsen en vervolgens True
of False
opleveren afhankelijk van het feit of de relatie waar is of niet.
Ziehier de werking van de 5 relaties >
, <
, ==
, <=
en >=
. Merk
op dat de gelijkheidsrelatie (men spreekt in de computerwetenschappen
eerder over een gelijkheidstest) met een dubbele ==
wordt genoteerd.
De enkele =
is in Python immers reeds gereserveerd voor de toekenning.
Merk ten slotte nog op dat !=
de Python versie is van wat wiskundigen
als \(\neq\) aanduiden.
45 > 34
True
45 > 79
False
45 < 79
True
45 < 34
False
45 == 34
False
10 <= 100
True
100 >= 200
False
Zoals we in het volgende experiment zien, zijn de relationele operatoren
stevig overladen. Men kan ze op haast alle types toepassen. Doorgaans
moeten beide operanden van hetzelfde type zijn om de test te kunnen
evalueren. Bij int
en float
is dat dan weer niet het geval. En alhoewel de float
67.0
en de int
67
niet hetzelfde getal zijn wordt ze toch als “van gelijke waarde” gezien door de ==
operator.
23.1 >= 23
True
23.1 >= 23.1
True
23.1 <= 23
False
67.0 == 67
True
Voor het vergelijken van strings wordt de zogenoemde lexicografische orde gebruikt. Dat is de orde die gebruikt wordt door woordenboekenschrijvers. Hieronder zien we enkele experimenten om het principe te illustreren:
'A' < 'a'
True
'A' > 'z'
False
'abc' < 'abd'
True
'abc' < 'abcd'
True
'hallo' == 'HalLo'
False
'hallo' == 'hallo'
True
'hallo' != 'hallo'
False
Predikaten¶
Relationele operatoren kunnen gebruikt worden in de body van functies
bijvoorbeeld om te bepalen een zekere eigenschap over de meegegeven
argumenten waar of vals is. Zulke functies die een booleaanse waarde
weergeven noemen we predikaten. Ze “prediken” als het ware iets over
de argumenten die aan de functie worden meegegeven. Hieronder zien we
hoe we een predicaat positive
kunnen schrijven dat True
of False
weergeeft afhankelijk van het feit of het argument een positief getal
is. De expressie in het return
statement past de operator >=
toe en
levert dus na evaluatie een booleaanse waarde op die meteen ook het
resultaat is van het predikaat.
def positive(x):
return x >= 0
positive(3)
True
positive(-2)
False
positive(0)
True
Hieronder zien we nog een predikaat dat gebruik maakt van een iets
ingewikkeldere booleaanse expressie. is_sin
bepaalt of een gegeven
getal al dan niet een geldige sinuswaarde is (m.a.w. of dat getal tussen
\(-1\) en \(1\) ligt). Dat doen we door de and
te gebruiken om 2
eenvoudige booleaanse expressies met elkaar te combineren.
def is_sin(x):
return (-1 <= x) and (x <= 1)
is_sin(-5)
False
is_sin(-0.6)
True
We kunnen is_sin
overigens ook als volgt schrijven. Python laat toe
dat je de twee relationele operatoren combineert in één enkele expressie
die veel meer leesbaar is.
def is_sin2(x):
return -1 <= x <= 1
is_sin(-5)
False
is_sin(-0.6)
True
and en or zijn lazy¶
Indien we terug kijken naar de definitie van or
, dan zien we dat het
resultaat steeds True
is van zodra het eerste operand True
is.
Vandaar dat or
op een “slimme” manier werkt en zich de moeite niet
meer getroost om het tweede operand uit te voeren van zodra het eerste
operand True
is. Men zegt dat or
een luie (Eng: lazy) operator is.
Dat wordt hieronder geïllustreerd.
def luie_or(x):
return positive(x) or (1/0 == "tof")
luie_or(1)
True
luie_or(-1)
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-79-d3c8d5482a50> in <module>
----> 1 luie_or(-1)
<ipython-input-77-29f0788c78a4> in luie_or(x)
1 def luie_or(x):
----> 2 return positive(x) or (1/0 == "tof")
ZeroDivisionError: division by zero
We definiëren hier een functie luie_or
die (als argumenten van de
or
) eerst positive
zal oproepen en daarna de zinloze bewerking
1/0 == "tof"
zal uitvoeren. In het experimentje zien we dat deze
zinloze bewerking niet wordt uitgevoerd indien het argument
daadwerkelijk een positief getal is. Dat komt omdat or
lazy is en zijn
rechteroperand niet uitvoert ingeval het linkeroperand True
is. Indien
het eerste operand echter False
oplevert, hangt het antwoord af van
het tweede operand (dat in dat geval dus ook uitgevoerd dient te worden)
wat een foutmelding oplevert. Dat illustreert de oproep van luie_or
met -1
.
Ook and
heeft een luie betekenis. Kan je deze ontcijferen op basis van
volgend experiment? Wanneer precies wordt het tweede operand van and
wél en niet uitgevoerd?
def luie_and(x):
return positive(x) and (1/0 == ":-(")
luie_and(-1)
False
luie_and(4)
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-82-1b6f135b5d5a> in <module>
----> 1 luie_and(4)
<ipython-input-80-c935d5588f3f> in luie_and(x)
1 def luie_and(x):
----> 2 return positive(x) and (1/0 == ":-(")
ZeroDivisionError: division by zero
Keuzes Maken met if
¶
Na deze omweg over booleaanse waarden, booleaanse operatoren, relationele operatoren en predikaten komen we terug bij het eigenlijke onderwerp van dit hoofdstuk: zélf functies schrijven in Python.
Sommige functies worden immers gedefinieerd door gevalsonderscheiding.
Dat zijn definities die we typisch m.b.v. accolades noteren. Een zeer
bekend voorbeeld is de definitie van de absolute waarde van een getal (Ptyhon kent abs
, we programmeren absolute
hier zelf om een idee te illustreren).
Zo’n “vertakkingen” in functiedefinities worden in Python m.b.v. het
if
statement bewerkstelligd. Hieronder zien we de definitie van een
functie absolute
die de absolute waarde van een getal berekent:
def absolute(x):
if x < 0:
return -x
else:
return x
absolute(5)
5
absolute(-5)
5
Algemeen gesproken, is if
een statement dat uit drie componenten
bestaan: een booleaanse expressie die men de test noemt, een block
code die men de then-tak noemt en een block code die men de else-tak
noemt. Bij aanvang van het if
statement zal Python de test uitvoeren
en afhankelijk van het resultaat (True
of False
) de then-tak dan wel
de else-tak uitvoeren.
In het algemeen ziet de if
test er dus als volgt uit:
Merk op dat de dubbele punten verplicht zijn, dat de sleutelwoorden if
en else
op hetzelfde niveau in de tekst dienen in te springen en dat
ook alle statements in beide blocks op hetzelfde niveau in te dienen
springen.
Ons positive
voorbeeld van hierboven zouden we ook als volgt met een
if
test kunnen schrijven: indien het argument groter of gelijk is aan
nul geven we True
weer; anders geven we False
weer.
Het is typerend voor beginnende programmeurs om deze definitie
makkelijker te vinden dan de definitie die hierboven werd gepresenteerd.
Computerwetenschappers zullen hier doorgaans niet mee akkoord gaan
aangezien de eerste definitie korter is en het type bool
als een
volwaardig algebraisch type behandelt. Maar nogmaals, over smaken en
kleuren …
def positive(x):
if x >= 0:
return True
else:
return False
positive(5)
True
positive(-3)
False
else
is optioneel**¶
In sommige gevallen dient er helemaal niks bijzonders te gebeuren indien
de test False
is. Om zulke gevallen toe te laten is de else-tak
optioneel. Het volgende stukje code laat zien hoe we de absolute waarde
nogmaals kunnen schrijven door de else-tak weg te laten.
def absolute2(x):
if x < 0:
return -x
return x
absolute2(5)
5
Merk op dat de body van absolute2
nu een block is dat 2 statements
bevat: een if
statement en een return
statement. Indien je
absolute2
oproept zal de body aan het werk beginnen en het eerste
statement uitvoeren. Dat is het if
statement. Indien de test waar is
zal de then-tak worden uitgevoerd en zal er dus teruggekeerd worden naar
de oproeper met -x
. Indien de test onwaar is wordt er overgegaan naar
het volgende statement van de body aangezien de if
test geen else-tak
heeft. Dit is het return x
statement wat in dit geval dus uitgevoerd
zal worden.
Het is doorgaans niet zo’n goede gewoonte om de else-tak weg te laten. Zoals hierboven wordt geïllustreerd kan je zulke code niet meer zuiver wiskundig lezen maar dien je werkelijk stap voor stap “computertje te spelen” om te begrijpen wat er gebeurt. Dat stap voor stap nabootsen van alle mogelijke uitvoeringen van een functie is doorgaans veel omslachtiger (en dus ontvankelijker voor fouten) dan het abstract wiskundig redeneren over een functie. Maar ook hier geldt weer het verhaal van smaken en kleuren.
Meerdimensionale Testen¶
Soms is het resultaat van een functie afhankelijk van meerdimensionale
input. Het volgende voorbeeldje toont een functie
risk_of_heart_disease
die op basis van iemands BMI en leeftijd
berekent of het risico op een hartaandoening laag, medium of hoog is. De
functie verwacht twee argumenten (een int
en een float
) en
produceert een string als resultaat. De werking van de functie kunnen we
samenvatten in de volgende tweedimensionale tabel:
Leeftijd/BMI |
\(<45\) |
\(\geq 45\) |
---|---|---|
\(\leq 22.0\) |
Laag |
Medium |
\(>22.0\) |
Medium |
Hoog |
Om zulke meerdimensionale testen te schrijven dienen we testen in elkaar
te nesten. Onze functie risk_of_heart_disease
heeft dus een body die
uit één enkel statement staat, namelijk een if
statement dat beslist
of we in de linker- dan wel de rechterkolom van de tabel zitten. Zowel
de then-tak als de else-tak van dat if
statement bestaan opnieuw uit
een if
statement dat de beslissing maakt of we in de onderste dan wel
de bovenste rij van de tabel zitten. Alle combinaties tesamen genomen,
zijn er dus \(4\) verschillende manier waarmee Python onze functie met een
string kan verlaten.
def risk_of_heart_disease(age, bmi):
if age < 45:
if bmi <= 22.0:
return "low"
else:
return "medium"
else:
if bmi <= 22.0:
return "medium"
else:
return "high"
Computerwetenschappers gebruiken de term “nesten” telkens ze een
samengesteld concept zien waarvan de samenstellende delen opnieuw dat
zelfde concept bevatten. Hier is dat een if
statement dat op zijn
beurt if
statements bevat. We hebben het eerder bijvoorbeed al over geneste tupels gehad. Dat zijn tupels waarvan de
componenten op hun beurt tupels zijn. We zullen in de volgende topics
nog verschillende voorbeelden van nesting tegenkomen.
Meerdere Takken: elif
¶
Tenslotte gebeurt het vaak dat het resultaat van een test meer dan twee
resultaten kan opleveren. Beschouw volgende functie category
die voor
een gegeven pH-waarde de correcte benaming van de chemische stof
teruggeeft. We spreken over sterke zuren, zwakke zuren, neutrale
stoffen, zwakke basen en sterke basen. Deze functie bestaat uit
verschillende if
statements die in mekaar genest zijn. Ieder if
statements vangt “de volgende” mogelijke uitkomst op en vermindert dus
het aantal nog mogelijke uitkomsten. Het is belangrijk de neststruktuur
van de functie te begrijpen: het is een if
statements genest in de
else-tak van een if
statements dat genest is in de else-tak van een
if
statements dat genest is in de else-tak van een if
statements dat
genest is in de else-tak van een if
statements!
def category(pH):
if 0 <= pH <= 4:
return "strong acid"
else:
if 5 <= pH <= 6:
return "weak acid"
else:
if pH == 7:
return "neutral"
else:
if 8 <= pH <= 9:
return "weak base"
else:
if 10 <= pH <= 14:
return "strong base"
else:
return "unknown ph"
category(7)
'neutral'
Het hoeft geen verder betoog dat dit zeer onhandig is. Het veelvuldig
nesten van if
statements geeft moeilijk te lezen code die bovendien
bijzonder makkelijk fout te interpreteren is. Merk verder op dat Python
eist dat alles correct ingesprongen is. Eén spatietje te veel of te
weinig maakt dat Python zich verslikt in de read fase!
Eigenlijk is bovenstaande code conceptueel gezien slechts een lange rij
van opeenvolgende testen die (in geval van True
) elk hun eigen tak
hebben. Er is dus eigenlijk slechts één echte else-tak. Om zulke
gevallen op te vangen laat Python een onbeperkt aantal elif
takken toe
tussen de then-tak en de else-tak. elif
is een afkorting voor “else
if”. Hieronder zien we een tweede versie van de functie category
die
gebruik maakt van elif
:
def category2(pH):
if 0 <= pH <= 4:
return "strong acid"
elif 5 <= pH <= 6:
return "weak acid"
elif pH == 7:
return "neutral"
elif 8 <= pH <= 9:
return "weak base"
elif 10 <= pH <= 14:
return "strong base"
else:
return "unknown ph"
category(7)
'neutral'
Merk op dat er een dubbele punt nodig is na elke test. Hieronder zien we
dus de algemene structuur van het if
statement in Python. Zowel de
elif
takken als de else
tak zijn optioneel. Het enige wat dus
absoluut verplicht is, is het sleutelwoord if
, de eerste test, de
dubbele punt en vervolgens een ingesprongen block dat uit minstens één
statement bestaat.
Functies vs. Procedures¶
In sectie 2 hebben we al het onderscheid tussen functies en procedures aangeraakt. Voor Python als programmeertaal zijn functies en procedures echter precies hetzelfde. Het zijn gewoon twee “soorten” van functies:
Functies worden opgeroepen vanuit expressies en leveren dus een waarde op. Indien een functie echter wordt opgeroepen en de evaluator komt tijdens de uitvoering van de body nergens een
return
statement tegen, dan “valt” Python uit de functie en wordt er eigenlijk implicietNone
als waarde teruggegeven.Procedures worden opgeroepen als statements (men verwacht immers toch niet dat ze een waarde teruggeven). Indien de aanroep van een procedure echter toch onverwacht aanleiding geeft tot het evalueren van een
return
statement dan negeert Python de eventueel teruggegeven waarde.
Hieronder zien we enkele stukjes Python die de grenzen aftasten van hoe
we functies, if
statements en return
statements op zinvolle manier
kunnen combineren. Het begrip van deze stukjes code is belangrijk om
jezelf ervan te vergewissen dat je alle begrippen goed begrepen hebt.
def f(x):
x
f(5)
type(f(5))
NoneType
None==f(5)
True
In de functie f
werd het return
statement vergeten. We noemen
deze functie dus een procedure aangezien ze geen resultaatwaarde
oplevert. Dat zien we hieronder bij de aanroep van f(5)
. We zien ook
dat evaluatie van type(f(5))
netjes NoneType
(t.t.z. het type van
None
) weergeeft. Er komt dus wel degelijk iets uit f
, alleen print de REPL het niet uit.
Hieronder volgen 3 pogingen om een functie te schrijven die het maximun weergeeft van haar 2 parameter.
def max1(x,y):
if x > y:
return x
else:
return y
max1(2,7)
7
max1(7,2)
7
De max1
functie bevat een if
statement met een else-tak. Wanneer de conditie x > y
True
oplevert wordt x
teruggegeven. Wanneer de conditie False
oplevert wordt y
teruggegeven. De functie werkt correct.
def max2(x,y):
if x > y:
return x
return y
max2(2,7)
7
max2(2,7)
7
De max2
functie bevat een if
statement zonder else-tak. Wanneer de conditie x>y
True
oplevert wordt x
teruggegeven. Wanneer de conditie echter False
oplevert is de if
test afgelopen want er is geen else-tak. Python gaat dan
gewoon over naar het volgende statement return y
waardoor de max2
ook hier het juiste resultaat geeft.
def max3(x,y):
if x > y:
return x
return y
max3(2,7)
De max3
functie lijkt heel sterk op de max1
functie. Het enige verschil is dat het
statement return y
anders is ingesprongen! Deze functie heeft een body die slechts uit één
statement bestaat: een if
statement waarvan de then-tak een block is
met 2 opeenvolgende return
statements. Uiteraard zal het tweede
return
statement nooit worden uitgevoerd aangezien de evaluator de
functie verlaat van zodra het eerste return
statement wordt
uitgevoerd. Dat zien we bij de oproep van max2(7,2)
waar we tich de x
terugkrijgen. Maar het loopt mis
bij de oproep van max3(2,7)
. Vermits de conditie False
oplevert
gaat Python naar hetvolgende statement. Dat statement bestaat niet en
dus vallen we uit max3
met None
als terugkeerwaarde.
Gevalstudies¶
In deze sectie laten we enkele gevalstudies zien die de kracht van Python functies aantonen. Het is de bedoeling dat je de code echt tot op het bot ontcijfert en dat je precies begrijpt welke van de begrippen uit dit hoofdstuk we in ieder voorbeeld gecombineerd hebben.
Wiskunde: Kwadratische Vergelijkingen¶
We beginnen met het oplossen van vergelijkingen van de vorm
\(a.x^2+b.x+c = 0\). Bedoeling is een functie quadratic
te schrijven,
die gegeven \(a\), \(b\) en \(c\), de wortel(s) oplevert. Iedereen weet dat we
daartoe dienen \(\Delta = b^2-4ac\) te berekenen en dat er afhankelijk van
het teken van \(\Delta\) geen, één of twee wortels zijn. Hier is de Python
code die dat voor ons doet:
from math import *
def quadratic(a,b,c):
delta = b*b - 4.0*a*c
if (delta == 0):
return - b / (2.0 * a)
elif delta > 0:
return ((- b + sqrt(delta)) / (2.0 * a),
(- b - sqrt(delta)) / (2.0 * a))
else:
return None
We focussen op de structuur van de code. De functie quadratic
heeft
een body die uit 2 statements bestaat: een toekenning gevolgd door een
if
test. De toekenning introduceert een lokale variabele delta
. De
if
test heeft 3 takken. Indien delta == 0
de waarde True
oplevert,
keert de functie terug met één wortel, namelijk een getal. Indien deze
test False
oplevert, wordt de elif
test geëvalueerd (t.t.z.
delta > 0
). Indien hier True
uit komt, geeft de functie een 2-tupel
terug waarin beide wortels als componenten zijn opgenomen. Na een oproep
van de vorm roots = quadratic(-1,10,10)
krijgen we bijvoorbeeld
toegang tot de wortels door roots[0]
en roots[1]
te evalueren. Ten
slotte wordt de waarde None
expliciet weer gegeven indien \(\Delta<0\).
Merk op dat dat laatste niet strikt noodzakelijk is. Python zou de
waarde automatisch weergeven indien we – door afwezigheid van een
else-tak – uit de functie zouden vallen. Code die dit expliciteert is
echter duidelijker leesbaar.
Fysica: Vectorrekening¶
In de fysica is het rekenen met vectoren één van de meest voorkomende wiskundige hulpmiddelen. Indien we bijvoorbeeld een truck een berg laten oprijden met een bepaalde snelheid dan kan deze snelheid worden voorgesteld door een 3-dimensionale vector \(\vec{v}_{truck}\) die 3 componenten heeft. Dat zijn 3 getallen die de snelheid in elke richting aanduiden. Indien intussen iemand op de oplegger van die truck wandelt met een bepaalde snelheid, dan wordt ook deze snelheid voorgesteld door een 3-dimensionale vector \(\vec{v}_{iemand}\). Om de totale snelheid van die persoon ten opzichte van de berg te kennen, dienen we beide vectoren componentsgewijs bij elkaar op te tellen: \(\vec{v}_{truck} + \vec{v}_{iemand}\).
In Python kunnen we 3-dimensionale vectoren voorstellen door 3-tupels.
We kunnen echter niet zomaar +
gebruiken om de componentsgewijze
optelling van vectoren te realiseren aangezien dat een 6-tupel oplevert.
Hier is een functie vector_sum
die de vectoriële som correct berekent.
Het resultaat ervan is een 3-tupel waarvan de componenten ieder uit de
som van de corresponderende componenten van de 2 gegeven vectoren
bestaan.
def vector_sum(v1,v2):
return (v1[0]+v2[0],v1[1]+v2[1],v1[2]+v2[2])
We kunnen ons arsenaal aan functies voor vectorieel rekenen uitbreiden
met vector_scal
en vector_inpr
. De eerste berekent de vector
\(k.\vec{v}\) indien \(\vec{v}\) een vector is en \(k\) een scalair. De tweede
berekent het inproduct \(\vec{v}_1 \times \vec{v}_2\) van twee vectoren.
def vector_scal(k,v):
return (k*v[0],k*v[1],k*v[2])
def vector_inpr(v1,v2):
return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]
We kunnen deze nu gebruiken om bijvoorbeeld de snelheid te berekenen van een deeltje. Stel dat een deeltje zich beweegt van een plaatsvector \(\vec{p}_1\) naar een plaatsvector \(\vec{p}_2\) tussen tijdstippen \(t_1\) en \(t_2\). De snelheidsvector die hiermee overeenkomt is dus:
waarbij \(\Delta \vec{r} = \vec{p}_2 - \vec{p}_1\) en waarbij \(\Delta t = t_2 - t_1\).
Vermits we weten dat \(\vec{v}_2 - \vec{v}_1\) eigenlijk hetzelfde is als
\(\vec{v}_2 + (-1.\vec{v}_1)\) en dat \(\frac{\Delta \vec{r}}{\Delta t}\)
eigenlijk hetzelfde is als \(\frac{1}{\Delta t} . \Delta \vec{r}\) krijgen
we de volgende Python functie. p1
en p2
stellen de beide
plaatsvectoren voor als een 3-tupel. Het resultaat is een 3-tupel dat de
snelheidsvector voorstelt.
def velocity(p1,p2,t1,t2):
delta_t = t2 - t1
delta_r = vector_sum(p2,vector_scal(-1.0,p1))
v = vector_scal(1.0/delta_t,delta_r)
return v
Chemie: Edelgassen Bepalen¶
In de volgende gevalsstudie schrijven we een functie edelgas
die het
atoomnummer van een element als enig argument neemt. Bedoeling is dat de
functie ofwel True
, ofwel False
weergeeft afhankelijk van het feit
of het element met dat atoomnummer een edelgas is. Hier is onze eerste
poging:
def edelgas1(Z):
if Z==2:
return True
elif Z==10:
return True
elif Z==18:
return True
elif Z==54:
return True
elif Z==86:
return True
elif Z==118:
return True
else:
return False
Deze functie is technisch helemaal correct. Nochtans zal iedere computerwetenschapper het een functie vinden die van weinig goede smaak getuigt. De volgende functie is helemaal equivalent maar bovendien al iets leesbaarder:
def edelgas2(Z):
return Z==2 or Z==10 or Z==18 or Z==36 or Z==54 or Z==86 or Z==118
Hier gebruiken we de ==
operator 7 keer. Iedere toepassing van de
operator zal True
of False
opleveren. De resultaten van deze testen
worden twee per twee gecombineerd met or
. Herinner dat de or
lui is.
Eens een test True
oplevert zullen de andere testen (van links naar
rechts) niet meer uitgevoerd worden
De derde versie van onze functie is de meest stijlvolle. Hier gebruiken
we de in
operator. De in
operator verwacht 2 argumenten: een element
aan de linkerkant en een tupel aan de rechterkant. De in
operator
geeft True
als en slechts als het element voorkomt in het tupel.
def edelgas3(Z):
return Z in (2,10,18,36,54,86,118)
Alle drie de “implementaties” van de functie edelgas
nemen een getal
als argument en produceren een booleaanse waarde als resultaat. De
functies laten zien dat er dikwijls verschillende programmeerstijlen
mogelijk zijn om hetzelfde te bereiken. Doorgaans verkiezen
computerwetenschappers de stijl die de kortst mogelijke code oplevert en
die “gewoon leest” zonder dat je de evaluator moet “nabootsen” om te
begrijpen wat er gebeurt. Dat is precies waarin de laatste implementatie
verschilt van de eerste implementatie.
Functies Debuggen met print
¶
Python functies zijn eigenlijk “zwarte dozen” waar je argumenten in stopt en waar een resultaat uitkomt. Als oproeper van een functie wil je je niet bezig houden met de manier waarop de functie de resultaten berekent. Dat hebben we procedurele abstractie genoemd. Maar wat als het fout gaat? Wat als je de functie oproept en je krijgt resultaten terug die niet overeenstemmen met wat je verwacht? In zo’n geval heb je te maken met een bug oftewel programmeerfout. De mythe wil dat de eerste computers die met radiolampen werden gebouwd verkeerde resultaten gaven indien er een insect op zo’n radiolamp zat. De mythe is haast zeker onwaar maar toch is het woord blijven bestaan in de computerwetenschappen. Het zoeken en verwijderen van programmeerfouten noemt men debuggen.
Beschouw volgende functie bad_quadratic
om vierkantsvergelijkingen op te
lossen.
def bad_quadratic(a,b,c):
delta = b*b - 4*a*c
if (delta == 0):
return -b // (2*a)
elif delta > 0:
return ((-b + sqrt(delta)) / (2*a),
(-b - sqrt(delta)) / (2*a))
else:
return None
Hier is wat er gebeurt indien we de vergelijking \(4x^2 + 4x+1 = 0\) proberen op te lossen.
bad_quadratic(4,4,1)
-1
None==print(4)
4
True
De uitkomst is niet wat we verwachten. We zien immers dat \(\Delta = 14-14 =0\) wat betekent dat de wortel \(-4 / 8 = -0.5\) moet zijn. Waar is het misgelopen?
Om te kunnen weten wat een functie allemaal aan het uitspoken is
alvorens het resultaat wordt teruggegeven bestaat er de print
functie. Elke oproep van print
zet iets op het scherm,
zélfs indien de REPL tijdelijk buiten spel staat omdat de body van de
functie nog in de evaluatiefase zit.
Hieronder zien we de print
functie aan het werk. Eigelijk is het een procedure, ze laat tekst op het scherm verschijnen maar ze geeft None terug.
Hieronder staan er een aantal opeenvolgende voorbeelden. Als je print
oproept met één enkel argument wordt die waarde op het scherm uitgeschreven. De volgende oproep van print begint dan aan een nieuwe lijn.
Als print wordt opgeroepen met meerdere argumenten worden die naast elkaar op 1 lijn uitgeschreven met bij default één spatie er tussenin. Wil je een andere separator kan je dat explciet aangeven in een optioneel argument sep
. Als je wil dat er na een print niet naar de volgende lijn wordt gesprongen kan je dat ook aangeven met de optionele parameter end
. De lijn wordt dan niet beëindigd met een “ga naar de nieuwe lijn” maar met de meegegeven waarde.
x=2
print(1)
print(x)
print(1+x)
print(4,5)
print((4,5))
print("resultaat=", 6)
print(7,"< resultaat <",7)
print("jan","willem")
print("jan","willem", sep="-")
print("jan", end=" ")
print("willem")
1
2
3
4 5
(4, 5)
resultaat= 6
7 < resultaat < 7
jan willem
jan-willem
jan willem
We schrijven als voorbeeld een
procedure verbose
die een string als argument neemt en dan een korte
conversatie op het scherm laat zien alvorens terug te keren naar de REPL
met de string "Voila!"
. Zorg dat je goed
begrijpt wat er gebeurt! Merkt op dat de 5 lijnen tekst op het scherm het resultaat zijn van de oproepen van print
. De string
"Voila!"
is de return waarde van de functie verbose
.
def verbose(name):
print ("Hello", " ", name, ",", sep="")
print ("I'm Python.")
name1 = "Wolf"
name2 = "Viviane"
opinion = "are really cool folks."
print ("Here's what I was just thinking:")
print (3*" ", name1, "and", name2, end=" ")
print (opinion)
print ("Going back to the REPL now. Bye!")
return "Voila!"
verbose("Mr. President")
Hello Mr. President,
I'm Python.
Here's what I was just thinking:
Wolf and Viviane are really cool folks.
Going back to the REPL now. Bye!
'Voila!'
Hoe kunnen we nu print
gebruiken om te zien wat er verkeerd was aan
onze bad_quadratic
functie? Ligt de fout bij het berekenen van delta
of
bij het berekenen van de expressie in de return
statement dat werkt
voor het geval dat er 1 wortel is? We zetten daarom een print
statement meteen na de berekening van delta
om de waarde van delta
te controleren en nog 1 net vóór het return
statement waarmee we de noemer van de breuk controleren.
def bad_quadratic(a,b,c):
delta = b*b - 4*a*c
print ("delta =", delta)
if (delta == 0):
print("2*a =", 2*a)
return -b // (2*a)
elif delta > 0:
return ((-b + sqrt(delta)) / (2*a),
(-b - sqrt(delta)) / (2*a))
else:
return None
bad_quadratic(4,4,1)
delta = 0
2*a = 8
-1
We zien nu datdelta
correct wordt berekend en de noemer van de breuk is
eveneens correct. Dat betekent dus dat het misloopt bij de deling. We
hebben de bug gevonden! We hebben ons bij hegt programmeren vergist en //
getikt
wat in Python de gehele deling is.
-4 // 8
geeft als uitkomst -1
en niet de verwachtte 0.5
Het euvel is makkelijk
verholpen door de gewone /
operator te gebruiken. Dan zal de uitkomst van de deling een float
getal worden. Probeer het!
Samenvatting¶
In dit topic hebben we geleerd van zelf functies te schrijven. We hebben
besproken hoe we op basis van argumenten een resultaat kunnen teruggeven
m.b.v. het return
statement. Dat staat in de body van de functie die
typisch uit meerdere statements kan bestaan. Deze vormen dan een block.
Sommige van deze statements kunnen toekenningen zijn en dat geeft dan
weer aanleiding tot lokale variabelen. Om onze body wat interessanter te
maken hebben we het if
statement bestudeerd. Dat bestaat uit een
conditie en een then-tak, een aantal optionele elif
takken en een
optionele else-tak. De conditie is een booleaanse expressie. Dat is een
expressie die een booleaanse waarde oplevert en die willekeurig complex
kan zijn door meerdere booleaanse expressies te combineren m.b.v.
booleaanse operatoren. Tevens hebben we relationele operatoren gezien.
Dit zijn eigenlijk ingebouwde predikaten die een relatie prediken over
twee argumenten. We hebben alles uitvoerig geïllustreerd in drie
relevante gevalstudies. Ten slotte hebben we het print
statement
gezien dat ons toelaat dingen op het scherm te zetten tijdens de
uitvoering van een functie. Je kan dit statement gebruiken om Python
functies te debuggen.