Functies — één keuze, één naam
Functies schrijven met def, wat return van print scheidt, randgevallen afvangen met een guard bovenaan, en docstrings.
Hoe wil je leren?
Het probleem dat we oplossen
Twee groepen hebben hun gemiddelde cijfer nodig:
marks_a = [72, 45]
marks_b = [90, 33, 61]
print(round(sum(marks_a) / len(marks_a), 2))
print(round(sum(marks_b) / len(marks_b), 2))58.5
61.33De twee regels zijn bijna identiek; alleen de naam verschilt. Een derde groep betekent een derde regel, een vierde een vierde.
Het probleem is niet het typen — kopiëren is goedkoop. Het probleem is dat één keuze nu op meerdere plaatsen staat. Als gevraagd wordt het gemiddelde met één decimaal in plaats van twee te tonen, moet je elke regel vinden en aanpassen, en het missen van er één laat het programma op twee verschillende manieren gedragen — zonder foutmelding.
En wat met een lege lijst? sum([]) / len([]) deelt door nul en het programma stopt. Die controle zou apart aan elke regel toegevoegd moeten worden.
Een functie is een manier om één keuze één keer te schrijven en er een naam aan te geven. Daarna doet het aanroepen van de naam het werk, en een wijziging betekent maar één plek aanpassen.
Aan het einde van dit hoofdstuk kun je
- Een functie schrijven en aanroepen met
def - Uitleggen wat
returnvanprintscheidt — het belangrijkste punt hier - Een functie klein houden en beperkt tot één taak
- Randgevallen afvangen met een guard bovenaan
- Een docstring schrijven
Voorkennis: Comprehensions.
Een eerste functie
def average(values):
return round(sum(values) / len(values), 2)
print(average([72, 45]))
print(average([90, 33, 61]))58.5
61.33De vorm:
def— "Ik definieer een functie"average— de naam, volgens dezelfde regels als een variabelenaam(values)— de parameter, de deur waar data binnenkomt:en een ingesprongen blok — de body, precies als bijifofforreturn— een resultaat terugsturen
Het schrijven van average([72, 45]) is aanroepen. [72, 45] is het argument, wat daadwerkelijk wordt gestuurd; values is de parameter, de naam binnenin. Twee verschillende woorden, en beide verschijnen in foutmeldingen, dus het onderscheid is het waard om te bewaren.
Twee lege regels. Twee lege regels na een functiedefinitie zijn de Python-conventie. Python draait prima met minder, maar alle Python-code wordt zo geschreven, dus het is de moeite waard om de gewoonte aan te leren.
return en print zijn niet hetzelfde
Dit is de belangrijkste sectie van het hoofdstuk, en de grootste verwarring voor beginners.
def with_return(x):
return x * 2
def with_print(x):
print(x * 2)
a = with_return(5)
b = with_print(5)
print(a)
print(b)
print(a + 1)10
10
None
11De eerste twee 10s op het scherm laten de functies identiek lijken. Ze zijn het niet.
with_return gaf de waarde terug, dus a bevat 10 en kan erbij worden opgeteld — vandaar 11 op de laatste regel.
with_print toonde de waarde en gaf niets terug, dus b is None. Het getal verscheen op het scherm, en het programma kreeg niets. b + 1 zou een TypeError geven.
Houd de regel zo vast: print is voor een mens, return is voor het programma. Een functie die berekent zou moeten returnen; tonen gebeurt waar hij werd aangeroepen. Op die manier kan dezelfde functie de ene dag een scherm bedienen en de volgende dag een bestand.
Zonder return geeft een functie stilletjes None terug — en dat is dezelfde None die append en .sort() in hoofdstuk twaalf en dertien teruggaven. Nu zie je waarom ze dat deden: hun taak was om iets te veranderen, niet om iets terug te geven.
return stopt direct
def check(mark):
if mark >= 40:
return "pass"
return "fail"
print(check(72))
print(check(20))pass
failHet moment dat return draait, is de functie klaar — de regels erna draaien nooit. Daarom was geen else nodig: als de eerste return gebeurt, is de tweede regel onbereikbaar.
Deze vorm — return zodra een voorwaarde matcht — maakt de diepe if/else-bomen van hoofdstuk negen plat, en maakt code veel leesbaarder.
Randgevallen afhandelen — een guard
Onthoud het probleem waarmee dit hoofdstuk opende? De lege lijst:
def average(values):
return sum(values) / len(values)
print(average([]))ZeroDivisionError: division by zeroDe fix gaat helemaal bovenaan de functie:
def average(values):
if not values:
return 0.0
return round(sum(values) / len(values), 2)
print(average([72, 45]))
print(average([]))58.5
0.0if not values is hoofdstuk acht zijn truthiness-regel weer: een lege lijst is false, dus not maakt het true. Geen len(values) == 0 nodig.
Dit heet een guard: het afwijkende geval bovenaan afhandelen, en de rest schrijven zonder zorgen. Functies maken dit goedkoop — de controle wordt één keer geschreven en elke aanroeper profiteert ervan.
Is0.0het juiste antwoord? Ter discussie. Het voegt "niemand heeft een cijfer" samen met "iedereen scoorde nul". Echte programma's geven vaakNoneterug, of gooien een fout met hoofdstuk vierentwintig zijnraise.0.0blijft hier voor de eenvoud — maar weet dat het een keuze is, geen voor de hand liggend antwoord.
Docstrings
Een stukje tekst op de eerste regel van de functie wordt zijn documentatie:
def average(values):
"""Return the mean of values, or 0.0 when there are none."""
if not values:
return 0.0
return round(sum(values) / len(values), 2)
print(average([1, 2, 3]))
print(average.__doc__)2.0
Return the mean of values, or 0.0 when there are none.Wat het van een comment scheidt is dat het in het draaiende programma overleeft — editors tonen het, en help(average) print het.
Een goede docstring zegt wat er terugkomt en wat er bij de randen gebeurt, niet hoe de code werkt. De code staat immers eronder.
Een compleet voorbeeld
report.py:
# Three small functions, each doing one thing
def average(values):
"""Return the mean of values, or 0.0 when there are none."""
if not values:
return 0.0
return round(sum(values) / len(values), 2)
def grade(mark):
"""Turn a mark into a letter."""
if mark >= 80:
return "A"
if mark >= 70:
return "B"
if mark >= 40:
return "C"
return "F"
def summarise(name, marks):
"""Build one report line for a person."""
mean = average(marks)
return f"{name:<8} {len(marks):>2} papers avg {mean:>6} grade {grade(mean)}"
people = {
"rafi": [72, 88, 61],
"ahmed": [45, 38],
"dia": [],
}
for name, marks in people.items():
print(summarise(name, marks))rafi 3 papers avg 73.67 grade B
ahmed 2 papers avg 41.5 grade C
dia 0 papers avg 0.0 grade FVier dingen die opvallen.
Elke functie doet één taak. average gemiddelt, grade geeft een letter, summarise bouwt een regel. Geen van hen print — printen gebeurt in de lus helemaal onderaan, op één plek. Dus dit rapport naar een bestand schrijven betekent die ene regel aanpassen.
Functies roepen functies aan. summarise roept average en grade aan. Dit is het echte voordeel: groot werk samengesteld uit kleine, betrouwbare stukjes.
grade heeft geen else. Elke return stopt de functie, dus de voorwaarden blijven plat. Hoofdstuk negen zijn regel geldt nog steeds: strikste voorwaarde eerst — 40 voor 80 zetten zou iedereen een C geven.
dia's lege lijst brak niets. De guard gaf 0.0 terug, grade maakte er F van, en de rapportregel printte normaal. Het randgeval was één keer, op één plek, afgehandeld.
Wanneer het breekt
NameError: name 'greet' is not defined — maar de functie staat er toch Hij werd aangeroepen voordat zijn def-regel draaide. Python leest een bestand van boven naar beneden, en de naam bestaat pas nadat de def uitgevoerd is. Houd alle defs bovenaan en de aanroepende code eronder.
TypeError: average() missing 1 required positional argument: 'values' Geen argument gegeven — average() in plaats van average(marks). De melding noemt zelfs de ontbrekende parameter.
De functie geeft None terug Er is geen return, of er is een op de verkeerde inspringing — binnenin een if wiens voorwaarde niet matcht, bijvoorbeeld. Een andere veelvoorkomende oorzaak: print geschreven waar return bedoeld was.
Een variabele binnenin de functie is erbuiten niet zichtbaar Dat is verwacht — waarom is hoofdstuk eenentwintig zijn onderwerp. Voor nu: return wat erbuiten nodig is.
SyntaxError — geen dubbele punt op de def-regel Net als if en for eindigt een def-regel met een dubbele punt.
IndentationError: expected an indented block after function definition Een def heeft minstens één ingesprongen regel erna nodig.
Stap 4 van 6 — Voorspellen
Test je begrip
De functie print in plaats van te returnen. Wat printen de twee regels?
def double(x):
print(x * 2)
result = double(5)
print(result)- A10 None
- B10 10
- CNone 10
- D10
Twee returns en geen else. Wat wordt geprint?
def check(mark):
if mark >= 40:
return "pass"
return "fail"
print(check(72))- Apass
- Bpass fail
- Cfail
- DNone
De functie staat er in het bestand. Wat gebeurt er toch?
print(greet("rafi"))
def greet(name):
return "hello " + name- AEen `SyntaxError`
- Bhello rafi
- CEen `TypeError`
- DEen `NameError` — de functie werd aangeroepen voordat zijn `def`-regel draaide
Voor antwoorden heb je een account nodig
Log in om je antwoorden te controleren
De vragen staan hierboven, en ze in je hoofd uitwerken is waar het om draait. Log in om de antwoorden, de uitleg en de hints in drie niveaus te zien.
Jouw beurt
Schrijf een bestand genaamd text_tools.py met vier functies, elk met een docstring, en geen van hen die iets print:
clean(text)— verwijdert spaties en zet in lowercaseinitials(full_name)— maakt van"Rafi Ahmed""RA"word_count(text)— telt woorden, geeft0voor een lege stringsummarise(text)— roept de andere drie aan en geeft één leesbare regel terug
En dan aan het einde van het bestand, roep summarise aan op een paar strings en print de resultaten — dat is de enige plek waar iets geprint wordt.
Tenslotte drie experimenten:
- Roep
word_count("")aan. Wat had er gebeurd zonder de guard? - Vervang de
returnvansummarisedoor eenprint, sla dan het resultaat op in een variabele en print die. Wat zie je, en waarom? - Verplaats de aanroep naar
cleanboven haar eigendef, en lees de fout.
Het tweede is de waardevolste drie minuten van dit hoofdstuk. Eenmaal met eigen ogen het verschil tussen print en return zien betekent ze nooit meer verwarren.
Stap 6 van 6
Uitdaging — de hoofdstukquiz
Tien vragen van makkelijk naar moeilijk. De laatste zijn met opzet lastig.
Log in om de quiz te maken