Python typing Modul – Effektive Nutzung von Typ-Prüfern

Pythons typing Modul versucht, eine Möglichkeit des Typenhinweises zu bieten, um statischen Typ-Prüfern und Linters zu helfen, Fehler genau vorherzusagen.

Aufgrund der Notwendigkeit von Python, den Typ von Objekten zur Laufzeit zu bestimmen, wird es manchmal sehr schwierig für Entwickler herauszufinden, was genau im Code vor sich geht.

Selbst externe Typ-Prüfer wie die PyCharm IDE liefern nicht immer die besten Ergebnisse; sie sagen im Durchschnitt nur etwa 50% der Fehler korrekt voraus, laut dieser Antwort auf StackOverflow.

Python versucht, dieses Problem zu mildern, indem es das sogenannte Type Hinting (Typenannotation) einführt, um externen Typ-Prüfern bei der Identifizierung von Fehlern zu helfen. Dies ist eine gute Möglichkeit für den Programmierer, den Typ des oder der verwendeten Objekte während der Kompilierungszeit selbst anzudeuten und sicherzustellen, dass die Typ-Prüfer korrekt arbeiten.

Dies macht Python-Code auch für andere Leser viel lesbarer und robuster!

HINWEIS: Dies führt keine tatsächliche Typenprüfung zur Kompilierungszeit durch. Wenn das tatsächlich zurückgegebene Objekt nicht vom angedeuteten Typ war, gibt es keinen Kompilierungsfehler. Deshalb verwenden wir externe Typ-Prüfer, wie mypy, um Typenfehler zu identifizieren.

Python Typing Module – Empfohlene Voraussetzungen

Um das typing Modul effektiv zu nutzen, wird empfohlen, einen externen Typ-Prüfer/Linter zur Überprüfung der statischen Typübereinstimmung zu verwenden. Einer der am weitesten verbreiteten Typ-Prüfer für Python ist mypy, daher empfehle ich, es zu installieren, bevor Sie den Rest des Artikels lesen.

Wir werden mypy als statischen Typ-Prüfer in diesem Artikel verwenden, der installiert werden kann:

Sie können mypy auf jede Python-Datei anwenden, um zu überprüfen, ob die Typen übereinstimmen. Das ist so, als würden Sie Python-Code ‚kompilieren‘.

Nach dem Debuggen von Fehlern können Sie das Programm normal ausführen:

Jetzt, da wir unsere Voraussetzungen abgedeckt haben, versuchen wir, einige Funktionen des Moduls zu nutzen.

Type Hints / Typenannotationen

Bei Funktionen

Wir können eine Funktion annotieren, um ihren Rückgabetyp und die Typen ihrer Parameter anzugeben.

def print_list(a: list) -> None:
    print(a)

Dies informiert den Typ-Prüfer (in meinem Fall mypy), dass wir eine Funktion print_list() haben, die eine Liste als Argument nimmt und None zurückgibt.

def print_list(a: list) -> None:
    print(a)

print_list([1, 2, 3])
print_list(1)

Lassen Sie uns dies zuerst auf unserem Typ-Prüfer mypy ausführen:

vijay@JournalDev:~ $ mypy printlist.py 
printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
Found 1 error in 1 file (checked 1 source file)

Wie erwartet erhalten wir einen Fehler; da Zeile #5 das Argument als int anstatt als Liste hat.

Bei Variablen

Seit Python 3.6 können wir auch die Typen von Variablen annotieren, indem wir den Typ angeben. Aber das ist nicht obligatorisch, wenn Sie möchten, dass sich der Typ einer Variablen ändert, bevor die Funktion zurückkehrt.

# Annotates 'radius' to be a float
radius: float = 1.5

# We can annotate a variable without assigning a value!
sample: int

# Annotates 'area' to return a float
def area(r: float) -> float:
    return 3.1415 * r * r


print(area(radius))

# Print all annotations of the function using
# the '__annotations__' dictionary
print('Dictionary of Annotations for area():', area.__annotations__)

Ausgabe von mypy:

vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py
Success: no issues found in 1 source file
7.068375
Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}

Python Typing Module – Typenaliase

Das typing Modul bietet uns Typenaliase, die durch Zuweisung eines Typs zum Alias definiert werden.

from typing import List

# Vector is a list of float values
Vector = List[float]

def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
print(a)

Ausgabe

vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py
Success: no issues found in 1 source file
[2.0, 4.0, 6.0]

Im obigen Snippet ist Vector ein Alias, der für eine Liste von Gleitkommawerten steht. Wir können bei einem Alias einen Typen hinweisen, was das obige Programm tut.

Die vollständige Liste der akzeptablen Aliase finden Sie hier.

Lassen Sie uns noch ein Beispiel betrachten, das jedes Schlüssel-Wert-Paar in einem Wörterbuch überprüft und feststellt, ob sie dem Format Name:E-Mail entsprechen.

from typing import Dict
import re

# Create an alias called 'ContactDict'
ContactDict = Dict[str, str]

def check_if_valid(contacts: ContactDict) -> bool:
    for name, email in contacts.items():
        # Check if name and email are strings
        if (not isinstance(name, str)) or (not isinstance(email, str)):
            return False
        # Check for email xxx@yyy.zzz
        if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
            return False
    return True


print(check_if_valid({'vijay': 'vijay@sample.com'}))
print(check_if_valid({'vijay': 'vijay@sample.com', 123: 'wrong@name.com'}))

Ausgabe von mypy

vijay@JournalDev:~ $ mypy validcontacts.py 
validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
Found 1 error in 1 file (checked 1 source file)


Benutzerdefinierte Datentypen mit NewType() erstellen

Wir können die Funktion NewType() verwenden, um neue benutzerdefinierte Typen zu erstellen.

from typing import NewType

# Create a new user type called 'StudentID' that consists of
# an integer
StudentID = NewType('StudentID', int)
sample_id = StudentID(100)


Der statische Typ-Prüfer wird den neuen Typ behandeln, als wäre er eine Unterklasse des ursprünglichen Typs. Dies ist nützlich, um logische Fehler aufzudecken.

from typing import NewType

# Create a new user type called 'StudentID'
StudentID = NewType('StudentID', int)

def get_student_name(stud_id: StudentID) -> str:
    return str(input(f'Enter username for ID #{stud_id}:\n'))

stud_a = get_student_name(StudentID(100))
print(stud_a)

# This is incorrect!!
stud_b = get_student_name(-1)
print(stud_b)

Ausgabe von mypy

vijay@JournalDev:~ $ mypy studentnames.py  
studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
Found 1 error in 1 file (checked 1 source file)

Python Typing Module – Der Any-Typ

Dies ist ein spezieller Typ, der dem statischen Typ-Prüfer (in meinem Fall mypy) mitteilt, dass jeder Typ mit diesem Schlüsselwort kompatibel ist.

Betrachten Sie unsere alte Funktion print_list(), die jetzt Argumente jeden Typs akzeptiert.

from typing import Any

def print_list(a: Any) -> None:
    print(a)

print_list([1, 2, 3])
print_list(1)

Jetzt wird es keine Fehler geben, wenn wir mypy ausführen.

vijay@JournalDev:~ $ mypy printlist.py && python printlist.py
Success: no issues found in 1 source file
[1, 2, 3]
1

Alle Funktionen ohne Rückgabetyp oder Parametertypen werden implizit standardmäßig Any verwenden.

def foo(bar):
    return bar

# A static type checker will treat the above
# as having the same signature as:
def foo(bar: Any) -> Any:
    return bar

Sie können also Any verwenden, um statisch und dynamisch getippten Code zu mischen.

Fazit

In diesem Artikel haben wir über das Python typing Modul für Checkers gelernt, das im Kontext der Typenprüfung sehr nützlich ist und externen Typ-Prüfern wie mypy erlaubt, Fehler genau zu melden – Effektive Nutzung von Typ-Prüfern

Kostenlosen Account erstellen

Registrieren Sie sich jetzt und erhalten Sie Zugang zu unseren Cloud Produkten.

Das könnte Sie auch interessieren: