Werken met Pythons Typesysteem en Type Hints

Werken met Pythons Typesysteem en Type Hints

20 maart 2024
Erwin Matijsen
Geplaatst in Theorie


Inleiding

In Python zijn er verschillende gegevenstypes, zoals getallen en tekst, maar ook complexere types zoals lijsten en woordenboeken. Elk type bepaalt wat je ermee kunt doen. Met behulp van klassen kan je zelfs je eigen gegevenstypes maken. Python controleert niet op het juist gebruik van deze types, wat voor onverwachte bugs in je code kan zorgen. Gelukkig zijn er Type Hints om je te helpen deze fouten te voorkomen. In dit artikel leer je wat type hints zijn, maar om het waarom te begrijpen, leer je vooral over wat gegevenstypes zijn en hoe en wanneer Python gegevenstypes controleert.

Gegevenstypes

Programmeren is het werken met gegevens, en elk gegeven heeft een bepaald type. Je kunt bijvoorbeeld werken met getallen, of met tekst. Afhankelijk van het gegevenstype kun je bepaalde handelingen wel of niet uitvoeren. Met getallen kun je rekenen, met tekst niet. Getallen en tekst zullen vrij intuïtief aanvoelen, daar heb je in het dagelijkse leven ook mee te maken. Maar er zijn ook complexere gegevenstypes. In Python heb je bijvoorbeeld de list, een lijst met verschillende elementen. Of de dict, een container met sleutel-waarden paren.

python
geheel_getal = 1  # int
tekst = "Lorem Ipsum"  # str
mijn_lijst = [1, 2, 3, 4, 5]  # list

# dict
mijn_dict = { 
    "naam": "Guido",
    "rol": "Bedenker van Python",
    "level": 10
}

In bovenstaand voorbeeld maak je eerst een geheel getal aan. Dit is het gegevenstype int (van integer). Daarna maak je een tekst aan, dit heeft het gegevenstype str (van string). Het derde voorbeeld is een list en tot slot maak je een dict aan, kort voor dictionary, ofwel: woordenboek.

Naast de ingebouwde gegevenstypes zoals int, str, float en dict kun je ook je eigen gegevenstypes maken, door klassen te maken. De dict van hierboven zou je ook kunnen vormgeven als:

python
# Maak een klasse `User` aan
class User:

    def __init__(self, naam, rol, level):
        self.naam = naam
        self.rol = rol
        self.level = level

    def level_up(self):
        self.level += 1


# Maak een instantie van het type `User` aan
guido = User(naam="Guido", rol="Bedenker Python", level=10)
print(type(guido))  #  <class '__main__.User'>

# Level up!
guido.level_up()
print(guido.level)  # 11

De klasse User bepaalt de gegevens van het type (naam, rol, level) en wat je ermee kunt (een level omhoog gaan, in dit geval).

Het belangrijkste om te onthouden is: Een gegevenstype bepaalt wat je met het gegeven kunt doen.

Lees meer

Lees ook het hoofdstuk Eerste stappen uit onze gratis Basiscursus Python om meer te leren over gegevenstypen in Python.

Lees meer

Lees het hoofdstuk Werken met klassen uit onze gratis Basiscursus Python voor meer informatie over het werken met klassen in Python.

Typesystemen

Als je met gegevens - en dus met gegevenstypes - werkt, kunnen er verschillende dingen verkeerd gaan die specifiek met het type te maken hebben. Stel dat je de volgende functie hebt:

python
def hoofdletters(woord):
    """Zet het gegeven woord om in kapitalen"""

    woord_in_kapitalen = woord.upper()   
    return woord_in_kapitalen

Een eenvoudige functie, die de methode upper gebruikt om het woord in kapitalen om te zetten. Maar... upper is een methode die je alleen op tekst (str) kunt toepassen. Het volgende levert dus geen problemen op:

python
woord = hoofdletters(woord="test")
print(woord)  # TEST

Maar wat als je nu geen str opgeeft, maar een int?

python
getal = hoofdletters(woord=10)

# AttributeError: 'int' object has no attribute 'upper'

Je ziet dat als je een verkeerd gegevenstype opgeeft als argument, er een fout ontstaat. Een int heeft namelijk geen methode upper!

Voordat je leert hoe je dit kunt voorkomen, is het belangrijk om te begrijpen hoe verschillende talen omgaan met het controleren van gegevenstypes. Hoe een taal dit implementeert noem je een typesysteem. Je kunt typesystemen langs twee assen indelen:

  • Dynamisch of Statisch
  • Zwak of Sterk

Dynamisch of Statisch

De eerste as is of een taal gebruik maakt van dynamische of statische typering. Een taal is statisch getypeerd als het type van een variabele tijdens compileren bekend is. Het compileren is het omzetten van de hogere taal (bijvoorbeeld C++) naar een taal die de computer begrijpt (machinetaal of bytecode). Is de taal statisch getypeerd, dan moet elke variabele een type hebben tijdens het compileren. Je ziet statische typering met name - maar niet uitsluitend - bij talen die gecompileerd worden voordat ze uitgevoerd kunnen worden.

Een dynamisch getypeerde taal daarentegen, associeert gegevenstypen met de waarden tijdens de uitvoering van het programma. Vaak - maar niet uitsluitend - is dit bij geïnterpreteerde talen. Een geïnterpreteerde taal voert de broncode regel voor regel uit en zet het tijdens uitvoer om in machinetaal of bytecode.

Gecompileerde talen en geïnterpreteerde talen

Compileren en interpreteren zijn ingewikkelde concepten, met veel nuances. Het geeft niet als je het niet 100% begrijpt. Belangrijk is dat je begrijpt dat dynamisch getypeerde talen tijdens uitvoer de types controleren. Statische getypeerde talen doen dit bij compilatie.

Python is een dynamisch getypeerde taal. Je kunt daarom gerust bovenstaande functie hoofdletters definiëren, en er pas tijdens uitvoering achter komen dat er een fout ontstaat als er een int wordt opgegeven als argument.

In een statisch getypeerde taal zou je tijdens het compileren - dus vóór uitvoering - er al achter komen dat er een mogelijke fout is, omdat de compiler weet dat je een int niet in hoofdletters kunt omzetten. Deze bug haal je er dan dus al uit voordat je code wordt uitgevoerd.

Zwak of Sterk

Een tweede as waarop je het typesysteem kunt indelen is of het sterke of zwakke typering betreft. In een zwak getypeerde taal - zoals JavaScript - is onderstaande mogelijk:

javascript
10 + "10"
// "1010"

Het resultaat is een tekst, het getal is dus impliciet omgezet van getal naar tekst.

In een sterk getypeerde taal zou dit een fout opleveren, daar kun je (sommige) gegevenstypes alleen expliciet omzetten:

python
x = 10 + int("10")
print(x)  # 20

Python is een sterk getypeerde taal.

Introducing: Type Hints

Python is een dynamisch getypeerde en geïnterpreteerde taal. Een voordeel hiervan is dat je er snel in kunt ontwikkelen. Je hoeft niet steeds eerst te compileren voordat je uitvoert, en je hoeft je ook niet druk te maken om gegevenstypes. Het nadeel hiervan is dat Python over het algemeen langzamer is dan gecompileerde talen zoals C, C++ of Rust. Een ander nadeel zag je al: bugs die te maken hebben met gegevenstypes komen soms pas tijdens uitvoering naar boven.

Sinds Python 3 is hier gelukkig een oplossing voor: Type Hints. Type hints zijn optionele toevoegingen aan je code waarmee je aangeeft van welk type je variabelen, parameters en return values zijn. Hiermee wordt het mogelijk om met behulp van externe tools je programma statisch te controleren. Hiermee haal je potentiële bugs dus al uit je code voordat het uitgevoerd wordt. Het eerdere voorbeeld kun je herschrijven als:

python
def hoofdletters(woord: str) -> str:
    """Zet het gegeven woord om in kapitalen"""

    woord_in_kapitalen = woord.upper()   
    return woord_in_kapitalen

woord = hoofdletters("test")
getal = hoofdletters(10)

Op de eerste regel zie je dat de parameter nu verwacht van het type str te zijn, aangegeven met woord: str. Met -> str geef je aan dat de functie altijd een str teruggeeft.

Je kunt nu bijvoorbeeld mypy gebruiken om je code te controleren, vóórdat je de code publiceert / uitvoert.

shell
mypy main.py

main.py:9: error: Argument 1 to "hoofdletters" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)

Deze type hints zijn optioneel en worden genegeerd door de interpreter. Je kunt ze dus stapsgewijs toevoegen in je bestaande code, de ene functie annoteer je wel, de andere niet.

Overigens is het belangrijk om te onthouden dat je met type hints niet alle bugs opspoort. Stel dat je bovenstaande functie gebruikt met invoer van een gebruik, bijvoorbeeld via een formulier. Die invoer moet je dan nog steeds controleren! Voert de gebruiker een cijfer in, dan zal er nog steeds een fout ontstaan.

Voorbeelden

Het type hints systeem is erg uitgebreid, zie de cheatsheet voor een snel overzicht. Hier volgen een paar voorbeelden om je op gang te helpen.

python
# Type hint variabelen
woord: str = "Test"
level: int = 10

# Type hints voor `dicts`
mijn_dict: dict[str, int]

# Of als de waarden van verschillende waarden kunnen zijn
from typing import Union

mijn_dict: dict[str, Union[str, int]]

# Of hetzelfde, vanaf Python 3.10
mijn_dict: dict[str, str | int]

# Gebruik Optional voor waarden die `None` mogen zijn
from typing import Optional
x: Optional[str] = None

# Of hetzelfde, vanaf Python 3.10
x: str | None = None

# Gebruik een eigen klasse als type
class User:
    def __init__(self, email):
        self.email = email

def email_user(user: User) -> None:
    # E-mail user
    print(f"E-mail verstuurd naar {user.email}")

Conclusie

Type hints in Python bieden het beste van beide werelden - de flexibiliteit en snelheid van dynamische typering, aangevuld met meer controle en zekerheid door de optionele type hints. Een bijkomend voordeel is dat veel Python IDEs en tools de type hints gebruiken om betere code-analyse en autocomplete te bieden.

Je kunt eenvoudig met type hints beginnen in je projecten, doordat optioneel zijn. Het is prima om sommige stukjes code wel te annoteren, en andere niet. Installeer bijvoorbeeld mypy, en je kunt beginnen!

Over de auteur


Erwin Matijsen

Erwin Matijsen

Erwin is de oprichter van python-cursus.nl. In allerlei rollen heeft hij Python ingezet, van het eenvoudiger maken van zijn werk tot het opleveren van complete (web)applicaties. Met vrouw en kinderen woont hij in Havelte (Drenthe), midden in de prachtige natuur. Daar wandelt hij graag, zeker ook omdat de beste ingevingen tijdens een wandeling - weg van de computer - lijken te komen.



Contact

Vragen, opmerkingen?

Heb je vragen, opmerkingen, suggesties of tips naar aanleiding van deze blog? Neem dan contact met ons op, of laat het weten via Mastodon of LinkedIN.