Skip to main content

Datentypen in Python

Einleitung

Für die Einführung von Python 3.5 wurden auch Python-Verbesserungsvorschläge (bzw. Python Enhancement Proposal oder kurz "PIP") vorgelegt, mit denen die Angaben von Datentypen möglich wurde (siehe PEP 484 und PEP 483). Wie auch in der Dokumentation von Python 3.10 (hier) nachzulesen ist, gibt es mit dessen Einführung vielerlei Verbesserungen, die das Schreiben von Code in Python mit Angaben von Datentypen vereinfachen.

Datentypen von Variablen anzugeben ist in Python nicht zwingend, da der Typ interpretiert wird, sobald der Variable ein Wert zugewiesen wird. Doch die Angabe von Datentypen bevor Werte zugewiesen werden, kann hilfreich sein. Will man z.B bei der Entwicklung auf eine Autoverfollständigung zurückgreifen, die Kenntnis des Datentyps braucht, muss der Typ festgelegt werden. Ebenso kann bei der Ausführung vom Python-Code ein Fehler ausgegeben werden, wenn Datentypen nicht übereinstimmen und später zu kritischeren Problemen führen könnten.

In diesem Beitrag wird zusammengefasst, wie Datentypangaben in Python 3.10 elegant gehandhabt werden und zu lesbaren Code führen.

Optional

Soll eine Funktion einen Wert zurückgeben, bedient man sich der dem Schlüsselwort return. Es kann jedoch vorkommen, dass der zurückzugebende Wert nicht existiert (z.B. weil die Datei, aus der er ausgelesen werden soll nicht existiert). Das folgende Beispiel soll dies veranschaulichen:


def read_data() -> str:
    try:
        with open("file.txt", "r") as f:
            data = f.read()
        if data:
            return data
    except FileNotFoundError:
        print("Cannot find \"file.txt\".")

Hier soll der Inhalt der Datei file.txt ausgelesen werden und als Zeichenkette zurückgegeben werden (was durch den Zusatz -> str in der Funktionsdefinition gekennzeichnet wird).

Doch wird die Datei nicht gefunden, kann auch keine Zeichenkette zurückgegeben werden. Stattdessen wird der Block aus try und except die Fehlermitteilung in der Konsole ausgeben und es wird kein Wert zurückgegeben (bzw. es wird der Wert None zurückgegeben).

Für einen Nutzer der Funktion ist dies jedoch irreführend, da aufgrund des Zusatzes -> str eine Zeichenkette erwartet wird. Man könnte natürlich auch eine leere Zeichenkette (also "") zurückgeben, doch suggeriert das, dass die Datei file.txt leer ist, obwohl sie garnicht existiert. Hier leistet das Optional Abhilfe, das explizit so verwendet wird:


from typing import Optional

def read_data() -> Optional[str]:
    ...

Nun weiß jeder, der die Funktion benutzt, dass sie eine Zeichenkette oder None zurückgeben kann. Mit Python 3.10 gibt es jedoch eine vereinfachte Schreibweise:


def read_data() -> str | None:
    ...

Hier zeigt das Oder | an, dass entweder eine Zeichenkette oder None zurückgegeben wird.

Union

Um mit Variablen umzugehen, die einer von verschiedene Datentypen sein kann, konnte man die Variable mit dem Typ Any kennzeichnen:


from typing import Any

def what_is(i: Any) -> None:
    if isinstance(i, str):
        print(f"You passed the string: {i}")
    elif isinstance(i, int):
        print(f"You passed the integer: {i}")
    elif isinstance(i, float):
        print(f"You passed the float: {i}")
    else:
        print(f"You passed the (unhandled) {type(i)}: {i}")

Doch diese Herangehensweise hat den Nachteil, dass wirklich jeder Datentyp zulässig übergeben werden kann. Will man nur eine Auswahl von zulässigen Datentypen angeben, bedient man sich der Union:


from typing import Union

def what_is(i: Union[int, float, str]):
    if isinstance(i, str):
        print(f"You passed the string: {i}")
    elif isinstance(i, int):
        print(f"You passed the integer: {i}")
    elif isinstance(i, float):
        print(f"You passed the float: {i}")

Nun erkennt jeder, der die Funktion benutzt, dass nur Ganzzahlen (int), Gleitkommazahlen (float) und Zeichenketten (str) zulässige Datentypen sind. Auch hierfür gibt es seit Python 3.10 eine kurze Schreibform:


def what_is(i: int | float | str):
    ...

Callable

Man einer Funktion eine andere Funktion als Variable übergeben, z.B.:


def do_math(func) -> None:
    return func(3, 4)

add = lambda x, y: x + y

do_math(add)

Dies ist dann vorteilhaft, wenn die Funktion do_math() nicht zu wissen braucht, was die übergebene Funktion func macht, sondern sie lediglich ausführt. Allerdings weiß man nicht unmittelbar, was für eine Funktion an do_math() übergeben werden muss (also mit wie vielen Argumenten und mit welchem Rückgabewert). Dies erzielt man durch Callable:


from typing import Callable

def do_math(func: Callable[[int, int], int]) -> None:
    ...

Nun weiß man bereits durch die Definition der Funktion do_math, dass eine Funktion übergeben werden soll, die zwei Ganzzahlen entgegennimmt und eine Ganzzahl zurückgibt.

Leider gibt es hierfür noch keine Kurzschreibform. Aber mit PEP 677 wurde bereits eine vorgeschlagen.

Mit Python 3.11 soll der folgende Code dann ein Callable als Datentyp angeben:


def do_math(func: (int, int) -> int) -> None:
    ...