Website-Suche

Python Typing-Modul - Verwenden Sie Typprüfer effektiv


Seit Python 3.5 eingeführt, versucht das typing-Modul von Python, eine Möglichkeit zur Angabe von Typen bereitzustellen, um statischen Typprüfern und Lintern zu helfen, Fehler genau vorherzusagen.

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

Selbst externe Typprüfer wie PyCharm IDE liefern nicht die besten Ergebnisse; im Durchschnitt werden nur etwa 50% der Fehler korrekt vorhergesagt, laut dieser Antwort auf StackOverflow.

Python versucht, dieses Problem zu mildern, indem es das bekannteste Konzept der Typ-Hinweise (Typannotations) einführt, um externen Typprüfern zu helfen, Fehler zu identifizieren. Dies ist eine gute Möglichkeit für den Programmierer, während der Kompilierungszeit den Typ des verwendeten Objekts bzw. der Objekte anzugeben und sicherzustellen, dass die Typprüfer korrekt arbeiten.

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

HINWEIS: Dies führt nicht zu einer tatsächlichen Typprüfung zur Kompilierzeit. Wenn das tatsächlich zurückgegebene Objekt nicht vom gleichen Typ ist wie angegeben, wird es keinen Kompilierungsfehler geben. Deshalb verwenden wir externe Typprüfer, wie mypy, um jegliche Typfehler zu identifizieren.

Empfohlene Voraussetzungen

Um das typing-Modul effektiv zu nutzen, wird empfohlen, einen externen Typprüfer/Linter zu verwenden, um auf statische Typübereinstimmung zu überprüfen. Einer der am weitesten verbreiteten Typprüfer für Python ist mypy, daher empfehle ich, ihn zu installieren, bevor Sie den Rest des Artikels lesen.

Wir haben bereits die Grundlagen der Typüberprüfung in Python behandelt. Du kannst zuerst diesen Artikel durchgehen.

Wir werden mypy als statischen Typprüfer in diesem Artikel verwenden, der folgendermaßen installiert werden kann:

pip3 install mypy

Du kannst mypy auf jede Python-Datei ausführen, um zu überprüfen, ob die Typen übereinstimmen. Das ist so, als würdest du Python-Code ‚kompilieren‘.

mypy program.py

Nachdem Sie die Fehler behoben haben, können Sie das Programm normal mit folgendem Befehl ausführen:

python program.py

Jetzt, wo wir unsere Voraussetzungen geklärt haben, lass uns versuchen, einige der Funktionen des Moduls zu verwenden.

Typ Hinweise/Typ Annotationen

Über Funktionen

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

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

Dies informiert den Typprüfer (in meinem Fall mypy), dass wir eine Funktion print_list() haben, die eine list als Argument akzeptiert und None zurückgibt.

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

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

Lass uns das zuerst mit unserem Typ-Checker 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 die Zeile #5 das Argument als int und nicht als list hat.

Über Variablen

Seit Python 3.6 können wir auch die Typen von Variablen annotieren, indem wir den Typ angeben. Aber das ist nicht verpflichtend, 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'>}

Dies ist der empfohlene Weg, mypy zu verwenden, indem zuerst Typannotationen bereitgestellt werden, bevor der Typprüfer verwendet wird.

Typalias

Das typing Modul bietet uns Typalias, die durch die 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 Ausschnitt ist Vector ein Alias, der für eine Liste von Gleitkommawerten steht. Wir können auf einen Alias hinweisen, was das obige Programm tut.

Die vollständige Liste der akzeptablen Aliase ist hier angegeben.

Lassen Sie uns ein weiteres Beispiel ansehen, das jedes Schlüssel:Wert Paar in einem Wörterbuch überprüft und prüft, ob sie dem name:email Format 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)

Hier erhalten wir einen statischen Kompilierzeitfehler in mypy, da der name-Parameter in unserem zweiten Wörterbuch eine Ganzzahl (123) ist. Daher sind Aliase eine weitere Möglichkeit, eine genaue Typüberprüfung von mypy durchzusetzen.

Erstellen Sie benutzerdefinierte Datentypen mit NewType()

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 Typprüfer behandelt den neuen Typ, als ob er eine Unterklasse des ursprünglichen Typs wäre. Dies ist hilfreich, um logische Fehler zu erkennen.

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)

Jede Art

Dies ist eine spezielle Art, die den statischen Typprüfer (in meinem Fall mypy) informiert, dass jeder Typ mit diesem Schlüsselwort kompatibel ist.

Betrachten Sie unsere alte print_list()-Funktion, 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 Parameter-Typen verwenden standardmäßig implizit Any.

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 somit Any verwenden, um statisch und dynamisch typisierten Code zu mischen.

Schlussfolgerung

In diesem Artikel haben wir über das Python typing Modul gelernt, das im Kontext der Typüberprüfung sehr nützlich ist und externen Typprüfern wie mypy ermöglicht, genau Fehlermeldungen zu berichten.

Dies bietet uns eine Möglichkeit, statisch typisierten Code in Python zu schreiben, das von Design her eine dynamisch typisierte Sprache ist!

Referenzen

  • Python-Dokumentation für das Typsystem-Modul (Dies enthält umfangreiche Details zu weiteren Methoden in diesem Modul, und ich empfehle dies als sekundäre Referenz)
  • StackOverflow-Frage zu Typhinweisen (Dies bietet eine sehr gute Diskussion zu diesem Thema. Ich empfehle Ihnen dringend, dieses Thema ebenfalls zu lesen!)