Featured image of post Type hints in Python 3

Type hints in Python 3

Python heeft sinds versie 3.5 ondersteuning voor type hinting. Handig, want daarmee wordt je code voorspelbaarder, omdat je meer type-informatie kunt vastleggen. Wat type hinting precies is, hoe het werkt en hoe je het kunt gebruiken, leggen we je uit in dit artikel.

Met type hinting in Python 3 kun je voor variabelen aangeven welk type het is. Ook kun je voor functies aangeven wat de types van de parameters zijn en wat het return type is. De Python interpreter zelf doet niets met type hinting. Vandaar dat de functie ‘hinting’ heet. Je code wordt gewoon uitgevoerd, ook al kloppen de types niet. Dat is ook logisch, omdat type hinting anders bijvoorbeeld duck typing zou breken. Het hele punt van duck typing ("if it quacks like a duck, treat it like a duck") is dat het type er niet toe doet.

Waar type hinting wel voor gebruikt wordt, is voor je code editor. Je IDE kan nu aangeven wanneer je types niet matchen, zodat je tijdens het schrijven of lezen van code kunt zien dat het waarschijnlijk niet gaat werken of dat er iets niet klopt.

IntelliJ en PyCharm hebben sinds 2015 ingebouwde ondersteuning voor type hinting van Python. Ook Visual Studio Code heeft via de Pylance-plug-in ondersteuning voor het checken van type hints in je code, maar heeft het standaard uitstaan. Om het in VS Code aan te zetten, ga je naar je extensies. Zoek dan naar 'Pylance' en klik op het tandwiel en kies voor Extension Settings. Scrol naar onderen en selecteer uit het lijstje bij Type Checking Mode de optie basic of strict.

Als je types wilt checken tijdens je build pipeline, dan kun je overigens gebruikmaken van een third-party tool zoals mypy.

Een voorbeeld van type hinting ziet er zo uit:

def greeting(name: str) -> str:
  return 'Hello ' + name

Je ziet hier dat we een parameter accepteren van het type string en dat de functie zelf ook een string teruggeeft. Voor een lijst met types die je in Python kunt gebruiken, check de docs. Je vindt hier bijvoorbeeld ook Union, dat is een combinatie van meerdere types, lists, TypeVar voor generics en meer.

Laten we nu kijken naar wat geavanceerdere functies van type hinting. Je kunt bijvoorbeeld zelf aliassen voor types maken, zoals:

Vector = list[float]

En je kunt ook geheel nieuwe types maken via de NewType helper:

PostId = NewType('PostId', int)
my_post = PostId(1234)

Dat is allemaal erg handig. Een andere, handige feature van type hints is dat je in Python ook dictionary’s van types kunt voorzien, door middel van de TypedDict class. Je maakt dan een nieuwe class die TypedDict extendt en definieert daarin welke keys en per key welke type je als value verwacht.

Duck typing

Duck typing noemden we net al even. Type hinting heeft speciaal ondersteuning voor duck typing in de vorm van Protocol.
Stel we hebben de volgende eend:

class Duck:
  def quack(self) -> str:
    return "Quack!"

def make_a_sound(duck: Duck) -> None:
  print(duck.quack())

make_a_sound(Duck())

Als we nu onze make_a_sound methode ook voor andere types willen gebruiken, hoeven we dankzij duck typing niets te doen, omdat make_a_sound alle objecten accepteert zolang ze maar een quack() methode hebben. Als we meer zekerheid willen hebben dat we de make_a_sound functie alleen aanroepen met classes die ook echt kunnen quacken, dan kunnen we een protocol definiëren:

from typing import Protocol

class CanQuack(Protocol):
  def quack(self) -> str:
    pass

class Duck:
  def quack(self) -> str:
    return "Quack!"

class BathtubDuck:
  def quack(self) -> str:
    return "Blub blub"

def make_a_sound(duck: CanQuack) -> None:
  print(duck.quack())

make_a_sound(Duck())
make_a_sound(BathtubDuck())

We definiëren hier het protocol CanQuack, waarin een methode zit genaamd quack(). Onze methode make_a_sound accepteert daardoor objecten die voldoen aan dit protocol. De typechecker controleert of een object voldoet aan het Protocol op basis van structural subtyping, wat betekent dat de type checker types vergelijkt op basis van de interne structuur van een class. Dat heeft als gevolg dat de classes Duck en BathtubDuck dus niets hoeven te implementeren of extenden van het Protocol. Er wordt alleen gekeken naar de interne structuur van de classes, en of ze de methode quack() bevatten.

Naast structural subtyping is er ook nominal subtyping, waarbij je de types expliciet moet noemen, bijvoorbeeld een class die ge-extend wordt of een interface die geïmplementeerd wordt.

Je kunt typing.Protocol gebruiken vanaf Python 3.8. Voor eerdere versies van Python, kun je gebruik maken van de pypi package typing_extensions.Protocol.

Conclusie

We hebben uitgelegd wat het nut is van type hinting en hoe je het kunt gebruiken als je Python-code schrijft. Ook hebben we gekeken naar hoe type hinting zich verhoudt tot duck typing door het gebruik van Protocols. Type hinting is een goede toevoeging aan Python 3 om je code leesbaarder te maken en meer zekerheid te geven.