12 Typannotationen
Typannotation (Type Annotation) ist eine Möglichkeit in Python, den erwarteten Typ von Variablen, Funktionsparametern und Rückgabewerten anzugeben.
Obwohl Python eine dynamisch typisierte Sprache ist, erlaubt die Typannotation eine bessere Lesbarkeit, Wartbarkeit und eine reduzierte Fehleranfälligkeit im Code.
Typannotation wird mit einem Doppelpunkt : und dem Typnamen gemacht. Der Rückgabetyp einer Funktion wird mit -> angegeben.
def addiere(x: int, y: int) -> int:
return x + y
In diesem Beispiel wird angegeben, dass sowohl x als auch y vom Typ int sind und die Funktion einen int zurückgibt.
name: str = "Anna"
alter: int = 30
aktiv: bool = True
Manchmal kann eine Variable auch None sein. Dafür verwendet man Optional:
from typing import Optional
def finde_benutzer(id: int) -> Optional[str]:
if id == 1:
return "Anna"
return None
AnyDer Typ Any aus dem typing-Modul signalisiert, dass jede Art von Wert erlaubt ist. Dies ist hilfreich, wenn der genaue Typ nicht bekannt oder variabel ist.
from typing import Any
def drucke_wert(wert: Any) -> None:
print(wert)
CallableCallable wird verwendet, um Funktionen als Parameter oder Rückgabewerte zu typisieren.
from typing import Callable
def verarbeite(funktion: Callable[[int, int], int]) -> int:
return funktion(2, 3)
In diesem Beispiel ist funktion eine Funktion, die zwei int-Werte nimmt und einen int zurückgibt.
Auch in Klassen können Typannotationen verwendet werden:
class Person:
name: str
alter: int
def __init__(self, name: str, alter: int) -> None:
self.name = name
self.alter = alter
Will man innerhalb einer Klasse auf die eigene Klasse verweisen (z. B. bei einer Methode, die ein Objekt der gleichen Klasse zurückgibt), verwendet man einen String (um zirkuläre Importe zu vermeiden) oder ab Python 3.11 from __future__ import annotations:
class Knoten:
def __init__(self, wert: int, nachfolger: 'Knoten' = None) -> None:
self.wert = wert
self.nachfolger = nachfolger
Ab Python 3.11:
from __future__ import annotations
class Knoten:
def __init__(self, wert: int, nachfolger: Knoten = None) -> None:
self.wert = wert
self.nachfolger = nachfolger
Man kann eigene Typen definieren, um komplexe Strukturen lesbarer zu machen:
from typing import List, Tuple
Koordinaten = Tuple[float, float]
Pfad = List[Koordinaten]
Python bleibt trotz Typannotationen dynamisch. Die Typangaben werden zur Laufzeit nicht erzwungen. Sie dienen lediglich als Hilfe für Entwickler und Werkzeuge.
def echo(text: str) -> str:
return text
# Funktioniert trotzdem, obwohl ein falscher Typ übergeben wird
print(echo(123)) # Ausgabe: 123
Typen vergleichen kann man mit den Funktionen type() und isinstance().
Beispiel:
from collections import namedtuple
Point = namedtuple('Punkt', ['x', 'y'])
p = Point(2, 3)
if type(p) == tuple:
print('P ist ein Tupel.') # Wird nicht ausgegeben!
if isinstance(p, tuple):
print('P ist ein Tupel.')
Es ist in der Regel besser, isinstance() anstelle eines direkten Vergleichs mit type() zu verwenden, weil isinstance() auch Vererbungen berücksichtigt.
type() gibt die exakte Klasse des Objekts p zurück, nämlich Point. Daher wird in dem Beispiel der Vergleich falsch ausgewertet.isinstance() überprüft, ob p eine Instanz von tuple oder einer Unterklasse davon ist. Da namedtuple eine Unterklasse von tuple ist, gibt isinstance(p, tuple) True zurück.Siehe auch: type(), isinstance() und issubclass().
Typannotationen machen Python-Code robuster, verständlicher und besser wartbar, ohne die Flexibilität der Sprache einzuschränken. Es lohnt sich, sie konsequent zu verwenden, besonders bei größeren Projekten.