Parameters en retourwaarden met echte typen annoteren.
Elementtypen en standaardwaarden afzonderlijk beschrijven.
Typeannotaties onderscheiden van expliciete invoercontrole.
Een functienaam vertelt vaak wat een bewerking doet, maar niet welke waarden de aanroeper hoort mee te geven. Typeannotaties maken die bedoeling zichtbaar naast de parameters en de retourwaarde. Je hebt ze al bij dataclassvelden gezien; nu gebruik je ze bij gewone functies.
Een leesbaar contract bij de functie
Een typeannotatie beschrijft hier het bedoelde type van een parameter of resultaat. In def twice(value: int) -> int: hoort value een geheel getal te zijn en hoort het resultaat ook een geheel getal te zijn. De berekening staat nog steeds in de functiebody.
De dubbele punt hoort bij een parameter; de pijl beschrijft het resultaat. Je hoeft bij de aanroep geen typen mee te geven. twice(4) blijft een gewone functieaanroep.
Het eerste voorbeeld gebruikt get_type_hints uit typing om de echte annotaties op te vragen. Die functie levert een dictionary met de parameternamen en de sleutel return. Zo kun je de aanwezige declaratie onderzoeken zonder af te gaan op afgedrukte broncode.
De echte annotaties van een functie onderzoeken
from typing import get_type_hints
def twice(value: int) -> int:
return value * 2
print(twice(4))
hints = get_type_hints(twice)
print(hints['value'] is int)
print(hints['return'] is int)
Verwachte uitvoer
8
True
True
get_type_hints gebruiken we alleen op eigen, vertrouwde functies. Het opvragen kan annotaties evalueren en is dus geen veilige controle op willekeurige onbekende code. In deze leeromgeving gebeurt die observatie binnen de bestaande sandbox.
Een annotatie voert de berekening niet uit
De annotaties vervangen geen return, lus of invoercontrole. Zij maken de afspraak leesbaar en leveren informatie voor gereedschappen die met types werken. Deze les voert geen statische typechecker uit en toont dus ook geen vermeende uitslag daarvan.
Python weigert een gewone functieaanroep niet automatisch omdat een argument afwijkt van de annotatie. Of de functie vervolgens werkt, hangt af van haar echte bewerkingen. Dat onderscheid is belangrijk wanneer je invoer van buiten je programma ontvangt.
Een eigen controle weigert de invoer werkelijk
def twice(value: int) -> int:
return value * 2
def checked_twice(value: int) -> int:
if type(value) is not int:
raise TypeError("waarde moet een int zijn")
return value * 2
print(twice([3]))
print(checked_twice(3))
try:
checked_twice([3])
except TypeError as error:
print(type(error).__name__ + ": " + str(error))
Verwachte uitvoer
[3, 3]
6
TypeError: waarde moet een int zijn
In dit voorbeeld kan vermenigvuldigen ook met een lijst werken, ondanks de annotatie. De tweede functie heeft daarnaast een eigen if die een verkeerde invoer werkelijk weigert. Die weigering komt van die expliciete code, niet van de dubbele punt.
Typen van elementen beschrijven
Met list[int] beschrijf je een lijst waarvan de elementen gehele getallen horen te zijn. Het buitenste type blijft een lijst. De notatie zet geen bestaande lijst om en loopt niet vanzelf alle elementen na.
Gebruik straks counts: list[int] voor de invoer van total_counts. De functie telt de waarden op en geeft een int terug. Een lege lijst heeft volgens de opdracht totaal nul; de ontvangen lijst blijft intact.
Een standaardwaarde blijft een aparte afspraak
Je kunt een annotatie en een standaardwaarde combineren, zoals step: int = 1. De annotatie beschrijft het bedoelde type, terwijl de toekenning bepaalt welke waarde bij een ontbrekend argument wordt gebruikt. Een expliciet meegegeven nul blijft dus nul.
Bij bump annoteer je beide parameters en het resultaat met int. De bestaande optelling blijft dezelfde. Verander ontvangen argumenten niet met een extra int(...) om alleen aan de annotatie te lijken te voldoen.
Veelgemaakte fout: veiligheid aan een annotatie overlaten
Een parameter value: int bewijst niet dat een ontvangen waarde werkelijk geschikt is. Voor require_count vraagt de opdracht daarom om echte validatie. Alleen het concrete type int is toegestaan; bool wordt hier expliciet afgewezen, ook al is bool in Python een subklasse van int.
Gebruik voor die specifieke afspraak type(value) is int. Weiger een ander type met TypeError en een negatief geheel getal met ValueError, met de opgegeven meldingen. Nul is een geldig aantal en wordt ongewijzigd teruggegeven.
Een zelfgemaakte subklasse van int valt volgens deze concrete-typeafspraak eveneens buiten de toegestane invoer. De annotatie alleen zou dat onderscheid niet afdwingen.
AAN DE SLAG 0/4
1
Parameters en resultaat annoteren
De body van repeat is gegeven. Voeg precies text: str, count: int en -> str toe. Laat de berekening gelijk: de annotaties voeren geen conversie of runtimecontrole uit, dus een lijst die met count vermenigvuldigd kan worden blijft ook een lijst.
2
Het type van lijstinhoud beschrijven
Annoteer total_counts met counts: list[int] en -> int. De bestaande sum telt op, geeft voor een lege lijst nul en verandert de ontvangen lijst niet. Voeg geen conversies toe.
3
Een annotatie en standaardwaarde combineren
Annoteer value en step van bump met int en het resultaat met int. Bewaar step=1 en de gewone optelling; een expliciete step=0 blijft nul. De annotaties voegen geen runtimecontrole of conversie toe.
4
Een echte invoercontrole toevoegen
Maak require_count(value: int) -> int. Geef een niet-negatieve concrete int ongewijzigd terug. Weiger andere typen, inclusief bool, met TypeError("aantal moet een int zijn"); weiger een negatieve int met ValueError("aantal mag niet negatief zijn").
Onthoud dit
Parameter- en retourannotaties beschrijven een bedoeld contract.
list[int] beschrijft ook het bedoelde elementtype.
Standaardwaarden en runtimevalidatie hebben ieder hun eigen werking.
Hierna beschrijf je dat een waarde soms ontbreekt, zonder nul of lege tekst als ontbrekend te behandelen.