42. Sanfte Einführung in Type Hints (optional)
In diesem Buch haben Sie Python-Code geschrieben, ohne anzugeben, welche Datentypen Ihre Variablen enthalten oder welche Typen Ihre Funktionen akzeptieren und zurückgeben. Python hat so vollkommen problemlos funktioniert—es ist eine dynamisch typisierte Sprache, das heißt, Typen werden zur Laufzeit bestimmt, während Ihr Programm ausgeführt wird. Diese Flexibilität ist eine der größten Stärken von Python und ermöglicht es Ihnen, Code schnell und ausdrucksstark zu schreiben.
Wenn Programme jedoch größer und komplexer werden, kann diese Flexibilität Code manchmal schwerer verständlich und wartbar machen. Wenn Sie eine Funktion wie def process_data(items): sehen, fragen Sie sich vielleicht: Welche Art von Daten enthält items? Eine Liste von Strings? Ein Dictionary? Etwas ganz anderes?
Type Hints (auch Type Annotations genannt) bieten eine Möglichkeit, die erwarteten Typen in Ihrem Code zu dokumentieren. Sie sind optionale Ergänzungen zu Python, die Ihren Code klarer machen können, helfen, Fehler früher zu finden, und leistungsstarke IDE-Funktionen ermöglichen—ohne zu verändern, wie Python Ihren Code tatsächlich ausführt.
Dieses Kapitel führt Sie behutsam in Type Hints ein, zeigt Ihnen, was sie sind, warum es sie gibt und wie Sie sie effektiv einsetzen. Weil Type Hints optional sind und nicht beeinflussen, wie Python Ihren Code ausführt, ist dieses gesamte Kapitel als optional markiert. Sie können hervorragende Python-Programme schreiben, ohne jemals Type Hints zu verwenden. Aber sie zu verstehen hilft Ihnen, modernen Python-Code zu lesen und zu entscheiden, wann sie Ihren eigenen Projekten nutzen könnten.
42.1) Warum Type Hints zu Python hinzugefügt wurden
Python wurde von Anfang an als dynamisch typisierte Sprache entworfen. Über Jahrzehnte hinweg schrieben Python-Programmierer Code ohne jegliche Typinformationen, und das funktionierte für unzählige Projekte wunderbar. Warum wurden also Type Hints 2015 (mit Python 3.5) zu Python hinzugefügt?
Die Herausforderung großer Codebasen
Als Python für Anwendungen im großen Maßstab beliebter wurde, stießen Teams auf Herausforderungen:
# In einer großen Codebase: Was erwartet diese Funktion und was gibt sie zurück?
def calculate_discount(customer, items, code):
# ... 50 Zeilen Code ...
return resultOhne den gesamten Funktionskörper oder die Dokumentation zu lesen, können Sie nicht erkennen:
- Ist
customerein Dictionary, ein eigenes Objekt oder etwas anderes? - Ist
itemseine Liste, ein Tuple oder ein Set? - Welchen Typ hat
code—ein String, eine ganze Zahl? - Was gibt die Funktion zurück—eine Zahl, ein Dictionary oder vielleicht
None?
In kleinen Programmen ist diese Unklarheit handhabbar. Sie können leicht nachsehen, wie die Funktion an anderer Stelle verwendet wird. Aber in einer Codebase mit Tausenden von Funktionen über Dutzende Dateien hinweg wird das schwierig.
Die Lösung: Optionale Type Hints
Die Entwickler von Python entschieden sich dafür, ein optionales System zum Dokumentieren von Typen hinzuzufügen. Das Schlüsselwort ist „optional“—Type Hints sind vollkommen freiwillig. Sie können sie verwenden, wenn sie helfen, sie ignorieren, wenn nicht, und annotierten sowie nicht annotierten Code frei mischen.
Hier ist ein einfaches Beispiel, das die grundlegende Syntax zeigt:
# Ohne Type Hints
def add(a, b):
return a + b
# Mit Type Hints
def add(a: int, b: int) -> int:
return a + bDie Syntax ist unkompliziert:
- Doppelpunkt (
:) nach einem Parameter zeigt, welchen Typ er haben sollte:a: int - Pfeil (
->) vor dem Doppelpunkt zeigt, welchen Typ die Funktion zurückgibt:-> int
Sehen wir uns das nun mit unserem früheren Beispiel an:
def calculate_discount(customer: dict, items: list, code: str) -> float:
# ... 50 lines of code ...
return resultJetzt ist sofort klar: customer ist ein Dictionary, items ist eine Liste, code ist ein String, und die Funktion gibt einen Float zurück.
Machen Sie sich keine Sorgen, wenn diese Syntax ungewohnt aussieht—wir werden sie in den Abschnitten 42.3–42.6 im Detail erkunden. Beachten Sie fürs Erste einfach, wie Sie auf einen Blick erkennen können, was die Funktion erwartet und was sie zurückgibt.
Mit oder ohne Type Hints funktioniert die Funktion exakt gleich—Python prüft diese Typen zur Laufzeit nicht. (Diesen wichtigen Punkt werden wir in Abschnitt 42.2 im Detail behandeln.)
Ein schrittweiser, pragmatischer Ansatz
Pythons Type-Hint-System wurde so entworfen, dass es:
- Optional ist: Sie müssen Type Hints nie verwenden
- Schrittweise ist: Sie können Hints zu einigen Teilen Ihres Codes hinzufügen und zu anderen nicht
- Nicht-invasiv ist: Hints ändern nicht, wie Python Ihren Code ausführt
- Tool-freundlich ist: Externe Tools können Hints prüfen, aber Python selbst ignoriert sie zur Laufzeit
Dieser pragmatische Ansatz lässt Python flexibel bleiben und bietet gleichzeitig Vorteile für alle, die sie nutzen möchten.
42.2) Die goldene Regel: Keine Durchsetzung zur Laufzeit
Das Wichtigste, das Sie über Type Hints verstehen müssen, ist dies: Python setzt Type Hints zur Laufzeit nicht durch. Sie sind rein informativ. Sehen wir uns an, was diese überraschende Realität in der Praxis bedeutet.
Type Hints verhindern keine falschen Typen
Betrachten Sie diese Funktion mit Type Hints:
def greet(name: str) -> str:
return f"Hello, {name}!"
# Das funktioniert problemlos, obwohl 42 kein String ist
result = greet(42)
print(result) # Output: Hello, 42!Der Type Hint sagt klar, dass name ein String sein sollte, aber Python akzeptiert die ganze Zahl 42 ohne Probleme und führt die Funktion aus. Python prüft den Type Hint nicht—es verwendet einfach den Wert, den Sie übergeben.
Das unterscheidet sich grundlegend von Sprachen wie Java oder C++, in denen der Compiler Typen prüft, bevor Ihr Code ausgeführt wird, und die Ausführung verweigert, wenn die Typen nicht passen. Pythons Ansatz ist toleranter: Es vertraut darauf, dass Sie die richtigen Typen liefern, zwingt Sie aber nicht dazu.
Das Problem: Risiken dynamischer Typisierung bleiben bestehen
Hier liegt die eigentliche Herausforderung: Selbst mit Type Hints bedeutet Pythons dynamische Typisierung, dass Sie weiterhin Typfehler machen können, die erst zur Laufzeit sichtbar werden:
def calculate_total(prices: list) -> float:
"""Berechnet die Summe der Preise."""
return sum(prices)
# Das funktioniert
print(calculate_total([10.99, 5.50, 3.25])) # Output: 19.74
# Aber das schlägt zur Laufzeit fehl!
print(calculate_total("not a list")) # TypeError: 'str' object is not iterableDer Type Hint sagt eindeutig, dass prices eine Liste sein sollte, aber Python hindert Sie nicht daran, einen String zu übergeben. Der Fehler erscheint erst, wenn der Code tatsächlich ausgeführt wird und versucht, sum() auf dem String zu verwenden.
Das ist frustrierend! Wir haben Type Hints hinzugefügt, um diese Probleme zu erkennen, aber die Risiken dynamischer Typisierung sind immer noch da. Typfehler können sich in Ihrem Code bis zur Laufzeit verstecken und möglicherweise in der Produktion auftreten, wenn ein Benutzer etwas Unerwartetes tut.
Wenn Type Hints also keine Laufzeitfehler verhindern, wozu sind sie dann gut?
Wofür sind Type Hints da?
Type Hints ändern möglicherweise nicht Pythons Laufzeitverhalten, aber sie erfüllen einen entscheidenden Zweck—sie liefern Informationen für Menschen und Tools, nicht für Python selbst:
- Dokumentation: Sie sagen Ihnen, welche Typen eine Funktion erwartet und zurückgibt
- IDE-Unterstützung: Ihr Editor kann Hints für Autovervollständigung nutzen und Warnungen anzeigen
- Statische Analyse: Externe Tools (wie mypy) können Ihren Code auf Typfehler prüfen, bevor Sie ihn ausführen
- Codeverständnis: Sie machen große Codebasen leichter lesbar und wartbar
Betrachten Sie Type Hints als Kommentare, die Tools verstehen können. Sie ändern nicht, wie Python läuft, aber sie helfen Ihnen, besseren Code zu schreiben.
Aber wie hilft uns das tatsächlich, diese Laufzeitfehler zu finden, die wir gerade gesehen haben?
Die Lösung: Type Hints + IDE-Unterstützung
Hier glänzen Type Hints wirklich. Während Python sie zur Laufzeit nicht durchsetzt, kann Ihre IDE Fehler erkennen, bevor Sie den Code überhaupt ausführen:
def add_numbers(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
# Ihre IDE zeigt hier eine Warnung (bevor Sie den Code ausführen)
result = add_numbers("Hello", "World") # IDE: Warning - expected int, got strIhr Code-Editor sieht die Type Hints und kann Sie auf Typabweichungen hinweisen, während Sie tippen, lange bevor Sie den Code ausführen. So werden viele Bugs während der Entwicklung statt in der Produktion gefunden.
Moderne Python-Entwicklung funktioniert typischerweise so:
- Sie schreiben Code mit Type Hints
- Ihre IDE zeigt Warnungen, wenn Typen nicht zusammenpassen
- Sie beheben die Probleme, bevor Sie den Code ausführen
- Laufzeitfehler durch Typabweichungen werden viel seltener
Der Type Hint verhindert den Fehler zur Laufzeit nicht, aber Ihre IDE nutzt ihn, um Sie davon abzuhalten, überhaupt fehlerhaften Code zu schreiben!
Das Beste aus beiden Welten
Type Hints geben Python das Beste aus beiden Welten—die meisten Fehler früh zu finden und gleichzeitig flexibel zu bleiben:
Sicherheit in der Entwicklung: Ihre IDE und Type Checker finden die meisten Typfehler während der Entwicklung, sodass Sie Bugs früh entdecken.
def process(data: list) -> list:
return [x * 2 for x in data]
# Wenn Sie versehentlich einen String übergeben:
process("hello") # IDE warns: expected list, got str
# Sie beheben es, bevor Sie den Code ausführen!Flexibilität zur Laufzeit: Python führt Code auch bei Typabweichungen aus, was für schnelles Prototyping nützlich sein kann oder wenn Sie absichtlich mehrere Typen akzeptieren wollen.
def add_numbers(a: int, b: int) -> int:
return a + b
# Python führt das aus, obwohl die Typen nicht passen
print(add_numbers(5.5, 3.2)) # Output: 8.7 (works!)
print(add_numbers("Hi", " there")) # Output: Hi there (also works!)Diese Flexibilität bedeutet, dass Sie nicht in einem starren Typsystem feststecken. Wenn Sie die Regeln brechen müssen (für Tests, Prototyping oder legitime Anwendungsfälle), lässt Python das zu. Aber wenn Sie Produktionscode schreiben, hält Ihre IDE Sie sicher.
Merken Sie sich die goldene Regel: Type Hints ändern nicht Pythons Laufzeitverhalten—sie geben nur Ihnen und Ihren Tools die Informationen, die nötig sind, um Probleme früh zu erkennen. Sie müssen weiterhin vorsichtig sein, aber jetzt haben Sie mächtige Verbündete, die Ihnen den Rücken freihalten.
42.3) Funktionen annotieren: Parameter und Rückgabewerte
Die häufigste Verwendung von Type Hints ist das Annotieren von Funktionsparametern und Rückgabewerten. Dadurch erfahren Leser (und Tools), welche Typen eine Funktion erwartet und erzeugt. Beginnen wir mit dem einfachsten Fall und steigern uns schrittweise.
Die Grundlagen: Parameter-Annotationen
Um einem Parameter einen Type Hint hinzuzufügen, setzen Sie nach den Parameternamen einen Doppelpunkt, gefolgt vom Typ:
def greet(name: str):
"""Begrüßt eine Person mit ihrem Namen."""
return f"Hello, {name}!"
# Usage
message = greet("Alice")
print(message) # Output: Hello, Alice!Die Syntax name: str bedeutet „der Parameter name sollte ein String sein“. Sie können Type Hints zu mehreren Parametern hinzufügen:
def calculate_area(width: float, height: float):
"""Berechnet die Fläche eines Rechtecks."""
return width * height
# Usage
area = calculate_area(5.0, 3.0)
print(area) # Output: 15.0Hier sind sowohl width als auch height als float annotiert. Die Funktion arbeitet wie zuvor—Type Hints verändern das Verhalten nicht—aber jetzt weiß Ihre IDE, welche Typen zu erwarten sind.
Rückgabetyp-Annotationen hinzufügen
Um anzugeben, welchen Typ eine Funktion zurückgibt, fügen Sie -> type nach der Parameterliste und vor dem Doppelpunkt hinzu:
def get_full_name(first: str, last: str) -> str:
"""Kombiniert Vor- und Nachnamen."""
return f"{first} {last}"
# Usage
name = get_full_name("John", "Doe")
print(name) # Output: John DoeDas -> str bedeutet „diese Funktion gibt einen String zurück“. Rückgabetyp-Annotationen sind besonders hilfreich, wenn der Rückgabetyp aus dem Funktionsnamen nicht offensichtlich ist:
def is_adult(age: int) -> bool:
"""Prüft, ob jemand erwachsen ist (18 oder älter)."""
return age >= 18
# Usage
adult = is_adult(25)
print(adult) # Output: TrueOhne in die Implementierung zu schauen, wissen Sie sofort, dass diese Funktion einen booleschen Wert zurückgibt.
Alles zusammen: Eine vollständige Funktion
Die meisten Funktionen haben sowohl Parameter- als auch Rückgabetyp-Annotationen. So sieht eine vollständig annotierte Funktion aus:
def calculate_discount(price: float, discount_percent: float) -> float:
"""Berechnet den reduzierten Preis."""
discount_amount = price * (discount_percent / 100)
return price - discount_amount
# Usage
original_price = 100.0
discount = 20.0
final_price = calculate_discount(original_price, discount)
print(f"Final price: ${final_price:.2f}") # Output: Final price: $80.00Diese Funktionssignatur sagt Ihnen alles, was Sie wissen müssen:
- Sie nimmt zwei
float-Parameter:priceunddiscount_percent - Sie gibt einen
float-Wert zurück - Sie müssen die Implementierung nicht lesen, um zu verstehen, wie Sie diese Funktion verwenden
Sehen wir uns ein weiteres Beispiel mit anderen Typen an:
def repeat_message(message: str, times: int) -> str:
"""Wiederholt eine Nachricht eine bestimmte Anzahl von Malen."""
return message * times
# Usage
repeated = repeat_message("Hello! ", 3)
print(repeated) # Output: Hello! Hello! Hello! Die Type Hints machen klar, dass Sie einen String und eine ganze Zahl übergeben und einen String zurückbekommen.
Arbeiten mit Standardwerten
Wenn ein Parameter einen Standardwert hat, platzieren Sie den Type Hint zwischen Parameternamen und Standardwert:
def create_greeting(name: str, formal: bool = False) -> str:
"""Erstellt eine Begrüßungsnachricht."""
if formal:
return f"Good day, {name}."
return f"Hi, {name}!"
# Usage
print(create_greeting("Alice")) # Output: Hi, Alice!
print(create_greeting("Bob", formal=True)) # Output: Good day, Bob.Die Syntax formal: bool = False bedeutet „formal ist ein Bool mit dem Standardwert False“.
Sie können mehrere Parameter mit Standardwerten haben, alle annotiert:
def format_price(amount: float, currency: str = "USD", decimals: int = 2) -> str:
"""Formatiert einen Preis mit Währungssymbol."""
if currency == "USD":
symbol = "$"
elif currency == "EUR":
symbol = "€"
else:
symbol = currency
return f"{symbol}{amount:.{decimals}f}"
# Usage
print(format_price(99.99)) # Output: $99.99
print(format_price(99.99, "EUR")) # Output: €99.99
print(format_price(99.995, "USD", 3)) # Output: $99.995Jeder Parameter zeigt klar seinen Typ und den Standardwert, wodurch die Funktion leicht zu verstehen und zu benutzen ist.
Spezialfall: Funktionen, die keine Werte zurückgeben
Einige Funktionen führen nur Aktionen aus (wie Drucken oder Schreiben in eine Datei), ohne einen Wert zurückzugeben. Um klarzumachen, dass diese Funktionen nichts zurückgeben, verwenden Sie -> None:
def print_report(title: str, data: list) -> None:
"""Druckt einen formatierten Bericht."""
print(f"=== {title} ===")
for item in data:
print(f" - {item}")
# Kein return-Statement, daher wird implizit None zurückgegeben
# Usage
print_report("Sales Data", [100, 150, 200])Output:
=== Sales Data ===
- 100
- 150
- 200Die Annotation -> None zeigt ausdrücklich an, dass diese Funktion keinen sinnvollen Wert zurückgibt.
Warum -> None verwenden?
- Klarheit: Es macht Ihre Absicht ausdrücklich—diese Funktion ist für Aktionen da, nicht für Ergebnisse
- IDE-Unterstützung: Ihre IDE kann Sie warnen, wenn Sie versehentlich versuchen, den Rückgabewert zu verwenden
42.4) Einfache Variablen-Annotationen
Während Type Hints am häufigsten bei Funktionen verwendet werden, können Sie auch Variablen annotieren. Sehen wir uns an, wie das funktioniert und wann es tatsächlich nützlich ist.
Grundlegende Syntax für Variablen-Annotationen
Um eine Variable zu annotieren, verwenden Sie dieselbe Doppelpunkt-Syntax wie bei Funktionsparametern:
# Variablen annotieren
name: str = "Alice"
age: int = 30
height: float = 5.7
is_student: bool = True
print(f"{name} is {age} years old") # Output: Alice is 30 years oldDie Syntax name: str = "Alice" bedeutet „die Variable name ist ein String und hat den Wert 'Alice'“. Die Annotation ändert nicht, wie die Variable funktioniert—sie ist rein informativ.
Variablen-Annotationen werden oft ausgelassen
In der Praxis werden Variablen-Annotationen selten verwendet. Der Grund ist einfach: Python kann den Typ aus dem Wert ableiten, daher sind Annotationen meist redundant:
# Diese Annotationen sind unnötig
name: str = "Alice" # Offensichtlich ein String
count: int = 0 # Offensichtlich ein int
prices: list = [10.99, 5.50] # Offensichtlich eine list
settings: dict = {} # Offensichtlich ein dict
# Schreiben Sie stattdessen einfach:
name = "Alice"
count = 0
prices = [10.99, 5.50]
settings = {}Wenn Sie name = "Alice" schreiben, wissen sowohl Sie als auch Ihre IDE sofort, dass es ein String ist. Die Annotation fügt keine nützliche Information hinzu.
In echtem Python-Code sehen Sie Variablen-Annotationen selten. Das ist normal und zu erwarten. Funktions-Annotationen sind deutlich wichtiger und häufiger.
Der eine nützliche Fall: Variablen vor der Zuweisung deklarieren
Es gibt eine Situation, in der Variablen-Annotationen wirklich nützlich sind: wenn Sie eine Variable deklarieren müssen, bevor Sie ihr einen Wert zuweisen.
def calculate_statistics(numbers: list) -> dict:
"""Berechnet grundlegende Statistiken aus einer Liste von Zahlen."""
# Variablen deklarieren, bevor sie verwendet werden
total: float
count: int
average: float
# Nun Werte zuweisen
total = sum(numbers)
count = len(numbers)
average = total / count if count > 0 else 0.0
return {
"total": total,
"count": count,
"average": average
}
# Usage
result = calculate_statistics([10, 20, 30, 40])
print(f"Average: {result['average']}") # Output: Average: 25.0Ohne Annotationen können Sie eine Variable nicht deklarieren, ohne ihr auch einen Wert zuzuweisen. Die Annotationen erlauben es Ihnen, die Typen vorab festzulegen, was die Code-Struktur klarer machen kann.
Das ist der wichtigste praktische Anwendungsfall für Variablen-Annotationen.
Merken Sie sich: Variablen können auf andere Typen neu zugewiesen werden
Selbst mit einer Typannotation können Sie einer Variablen einen anderen Typ zuweisen:
# Starten Sie mit einem String
value: str = "hello"
print(value) # Output: hello
# Neu zuweisen auf einen anderen Typ - Python erlaubt das
value = 42
print(value) # Output: 42
# Noch ein Typwechsel - weiterhin erlaubt
value = [1, 2, 3]
print(value) # Output: [1, 2, 3]Ihre IDE oder ein statischer Type Checker wird Sie vor diesen Typwechseln warnen, aber Python selbst verhindert sie nicht. Type Hints leiten Sie zu Konsistenz an, setzen sie jedoch zur Laufzeit nicht durch.
42.5) Umgang mit „None“: Optionale Typen und der | Operator
Eines der häufigsten Muster in Python ist eine Funktion, die vielleicht einen Wert zurückgibt oder vielleicht None zurückgibt. Zum Beispiel kann die Suche nach einem Element erfolgreich sein (das Element wird zurückgegeben) oder fehlschlagen (es wird None zurückgegeben). Type Hints bieten klare Möglichkeiten, dieses Muster auszudrücken.
Das Problem: Funktionen, die möglicherweise None zurückgeben
Betrachten Sie diese Funktion, die nach einem Benutzer sucht:
def find_user_by_email(email: str) -> dict:
"""Find a user by email address."""
users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]
for user in users:
if user["email"] == email:
return user
return None # Typabweichung! Das widerspricht dem -> dict Hint
# Usage
user = find_user_by_email("alice@example.com")
if user:
print(f"Found: {user['name']}") # Output: Found: Alice
else:
print("User not found")Der Type Hint -> dict ist irreführend, weil die Funktion None zurückgeben kann. Ein statischer Type Checker würde Sie warnen, dass das Zurückgeben von None nicht zum deklarierten Rückgabetyp dict passt.
Lösung: Den | Operator für optionale Typen verwenden
Python 3.10 hat den |-Operator für Type Hints eingeführt, der „oder“ bedeutet. Sie können ihn verwenden, um anzugeben, dass eine Funktion einen Typ oder einen anderen zurückgeben kann:
def find_user_by_email(email: str) -> dict | None:
"""Find a user by email address. Returns None if not found."""
users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]
for user in users:
if user["email"] == email:
return user
return None
# Usage
user = find_user_by_email("alice@example.com")
if user:
print(f"Found: {user['name']}") # Output: Found: Alice
missing = find_user_by_email("charlie@example.com")
if missing is None:
print("User not found") # Output: User not foundDer Type Hint -> dict | None bedeutet „diese Funktion gibt entweder ein Dictionary oder None zurück“. Das beschreibt das Verhalten der Funktion korrekt.
Hinweis: In älterem Python-Code (vor 3.10) sehen Sie möglicherweise Optional[dict] aus dem typing-Modul statt dict | None. Beides bedeutet dasselbe, aber | ist die moderne, bevorzugte Syntax.
| mit mehreren Typen verwenden
Sie können | verwenden, um mehr als zwei mögliche Typen anzugeben:
def parse_value(text: str) -> int | float | None:
"""Parse a string into a number. Returns None if parsing fails."""
try:
# Try parsing as integer first
if '.' not in text:
return int(text)
# Otherwise parse as float
return float(text)
except ValueError:
return None
# Usage
print(parse_value("42")) # Output: 42 (int)
print(parse_value("3.14")) # Output: 3.14 (float)
print(parse_value("invalid")) # Output: NoneDer Type Hint -> int | float | None bedeutet, dass die Funktion eine ganze Zahl, einen Float oder None zurückgeben kann.
Auf None prüfen: Best Practices
Wenn eine Funktion None zurückgeben kann, prüfen Sie immer auf None, bevor Sie das Ergebnis verwenden. Andernfalls riskieren Sie Fehler, wenn Sie None so verwenden wollen, als wäre es der erwartete Typ:
def get_user_age(user_id: int) -> int | None:
"""Get user's age. Returns None if user not found."""
users = {1: 25, 2: 30, 3: 35}
return users.get(user_id)
# Immer auf None prüfen, bevor Sie den Wert verwenden
age = get_user_age(1)
if age is not None:
print(f"User is {age} years old") # Output: User is 25 years old
if age >= 18:
print("User is an adult") # Output: User is an adult
else:
print("User not found")
# Für nicht existierende Benutzer
age = get_user_age(999)
if age is None:
print("User not found") # Output: User not foundDer Schlüssel ist, if age is not None: oder if age is None: zu verwenden, um vor der Nutzung ausdrücklich zu prüfen.
Optionale Parameter mit | None
Sie können | auch bei Parametern verwenden, oft kombiniert mit Standardwerten:
def format_name(first: str, middle: str | None = None, last: str = "") -> str:
"""Format a full name. Middle name is optional."""
if middle and last:
return f"{first} {middle} {last}"
elif last:
return f"{first} {last}"
return first
# Usage
print(format_name("John", "Q", "Doe")) # Output: John Q Doe
print(format_name("Jane", None, "Smith")) # Output: Jane Smith
print(format_name("Prince")) # Output: PrinceDer Type Hint middle: str | None = None zeigt an, dass middle ein String oder None sein kann, wobei None der Standardwert ist. Das ist ein gängiges Muster für optionale Parameter.
42.6) Häufige Type Hints lesen: list, dict, tuple
Wenn Sie Python-Code anderer lesen, werden Ihnen Type Hints für Collections wie Listen, Dictionaries und Tuples begegnen. Modernes Python bietet klare Möglichkeiten, nicht nur anzugeben, dass etwas eine Liste ist, sondern auch, welchen Typ die Elemente in der Liste haben.
Hinweis: Die hier gezeigte Syntax (list[int], dict[str, int] usw.) funktioniert in Python 3.9+. In älterem Code sehen Sie möglicherweise List[int] und Dict[str, int] (großgeschrieben) aus dem typing-Modul—sie funktionieren genauso.
Grundlegende Collection-Type-Hints
Die einfachsten Collection-Type-Hints geben nur den Collection-Typ an:
def print_items(items: list) -> None:
"""Print all items in a list."""
for item in items:
print(item)
def get_user_settings() -> dict:
"""Get user settings as a dictionary."""
return {"theme": "dark", "notifications": True}
def get_position() -> tuple:
"""Get x, y position."""
return (10, 20)Diese Hints sagen Ihnen den Collection-Typ, aber nicht, was darin enthalten ist.
Listen: Elementtypen angeben
Um anzugeben, welchen Typ die Elemente einer Liste haben, verwenden Sie eckige Klammern:
def calculate_total(prices: list[float]) -> float:
"""Calculate the total of all prices."""
return sum(prices)
# Usage
total = calculate_total([10.99, 5.50, 3.25])
print(f"Total: ${total:.2f}") # Output: Total: $19.74Der Type Hint list[float] bedeutet „eine Liste, die Floats enthält“. Das ist informativer als nur list.
Hier ist ein weiteres Beispiel mit Strings:
def format_names(names: list[str]) -> str:
"""Format a list of names as a comma-separated string."""
return ", ".join(names)
# Usage
students = ["Alice", "Bob", "Charlie"]
print(format_names(students)) # Output: Alice, Bob, CharlieDer Type Hint list[str] bedeutet „eine Liste, die Strings enthält“.
Dictionaries: Schlüssel- und Werttypen angeben
Bei Dictionaries geben Sie sowohl den Schlüsseltyp als auch den Werttyp an:
def get_student_grades() -> dict[str, int]:
"""Get student names mapped to their grades."""
return {
"Alice": 95,
"Bob": 87,
"Charlie": 92
}
# Usage
grades = get_student_grades()
for name, grade in grades.items():
print(f"{name}: {grade}")Output:
Alice: 95
Bob: 87
Charlie: 92Der Type Hint dict[str, int] bedeutet „ein Dictionary mit String-Schlüsseln und Integer-Werten“.
Hier ist ein Beispiel, bei dem Werte mehrere Typen haben können:
def get_user_data(user_id: int) -> dict[str, str | int]:
"""Get user data. Values can be strings or integers."""
return {
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"id": 12345
}
# Usage
user = get_user_data(1)
print(f"{user['name']} is {user['age']} years old") # Output: Alice is 30 years oldDer Type Hint dict[str, str | int] bedeutet „ein Dictionary mit String-Schlüsseln und Werten, die entweder Strings oder Integers sind“.
Tuples: Feste und variable Länge
Tuples unterscheiden sich von Listen, weil sie oft eine feste Struktur haben. Sie können den Typ jeder Position angeben:
def get_user_info(user_id: int) -> tuple[str, int, bool]:
"""
Get user information as a tuple.
Returns: (name, age, is_active)
"""
return ("Alice", 30, True)
# Usage
name, age, active = get_user_info(1)
print(f"{name}, {age}, active: {active}") # Output: Alice, 30, active: TrueDer Type Hint tuple[str, int, bool] bedeutet „ein Tuple mit genau drei Elementen: ein String, eine ganze Zahl und ein Bool, in dieser Reihenfolge“.
Für Tuples variabler Länge, deren Elemente alle denselben Typ haben, verwenden Sie Ellipsis (...):
# Tuple mit fester Länge: genau 2 floats
def get_2d_point() -> tuple[float, float]:
"""Get 2D coordinates (x, y)."""
return (10.5, 20.3)
# Tuple mit variabler Länge: beliebig viele floats
def get_coordinates() -> tuple[float, ...]:
"""Get coordinates. Can be 2D, 3D, or any dimension."""
return (10.5, 20.3, 15.7) # In diesem Fall 3D
# Usage
point = get_2d_point()
coords = get_coordinates()
print(f"2D point: {point}") # Output: 2D point: (10.5, 20.3)
print(f"Coordinates: {coords}") # Output: Coordinates: (10.5, 20.3, 15.7)Der Type Hint tuple[float, ...] bedeutet „ein Tuple mit beliebig vielen Floats“. Das ... bedeutet „beliebig viele dieses Typs“.
Verschachtelte Collections
Sie können Type Hints für komplexe Datenstrukturen verschachteln. Beginnen wir mit einem einfachen Beispiel:
def get_scores_by_student() -> dict[str, list[int]]:
"""Get test scores for each student."""
return {
"Alice": [95, 87, 92],
"Bob": [88, 91, 85],
"Charlie": [90, 88, 94]
}
# Usage
scores = get_scores_by_student()
for name, tests in scores.items():
average = sum(tests) / len(tests)
print(f"{name}: {average:.1f}")Output:
Alice: 91.3
Bob: 88.0
Charlie: 90.7Der Type Hint dict[str, list[int]] bedeutet „ein Dictionary mit String-Schlüsseln und Liste-von-Integers-Werten“.
Hier ist ein komplexeres Beispiel:
def get_student_records() -> list[dict[str, str | int]]:
"""Get a list of student records."""
return [
{"name": "Alice", "age": 20, "major": "CS"},
{"name": "Bob", "age": 21, "major": "Math"},
{"name": "Charlie", "age": 19, "major": "Physics"}
]
# Usage
students = get_student_records()
for student in students:
print(f"{student['name']}, {student['age']}, {student['major']}")Output:
Alice, 20, CS
Bob, 21, Math
Charlie, 19, PhysicsDer Type Hint list[dict[str, str | int]] bedeutet „eine Liste von Dictionaries, wobei jedes Dictionary String-Schlüssel und Werte hat, die entweder Strings oder Integers sind“.
Type Hints lesen: Eine schnelle Referenz
Wenn Sie in Code auf Type Hints stoßen, können Sie sie so lesen:
Collections:
list[int]- „eine Liste von ganzen Zahlen“dict[str, float]- „ein Dictionary mit String-Schlüsseln und Float-Werten“tuple[str, int]- „ein Tuple mit genau zwei Elementen: ein String, dann eine ganze Zahl“tuple[float, ...]- „ein Tuple mit beliebig vielen Floats“
Optional und mehrere Typen:
int | None- „eine ganze Zahl oder None“str | int | float- „ein String, eine ganze Zahl oder ein Float“
Verschachtelt:
list[dict[str, int]]- „eine Liste von Dictionaries (jedes dict hat String-Schlüssel und Integer-Werte)“dict[str, list[float]]- „ein Dictionary mit String-Schlüsseln und Liste-von-Floats-Werten“
Hinweis: In älterem Code (Python < 3.10) sehen Sie möglicherweise Union[int, str] statt int | str, oder Optional[int] statt int | None. Sie bedeuten dasselbe.