Das Typsystem von Python ist im Jahr 2026 mit Python 3.12+ deutlich ausgereifter geworden. PEP 695 (Typparametersyntax), PEP 696 (TypeVar-Standardeinstellungen) und das neuetype-Anweisung machen Python-Generika lesbarer. Dieser ausführliche Einblick behandelt fortgeschrittene Typmuster, die erfahrene Python-Entwickler kennen sollten.
📋 Table of Contents
Neue Syntax: type-Anweisung (Python 3.12+)
# Old way (still works)
from typing import TypeAlias, TypeVar
Vector = list[float]
T = TypeVar("T")
# New syntax (Python 3.12+, much cleaner)
type Vector = list[float]
type Matrix = list[Vector]
type JSON = str | int | float | bool | None | list["JSON"] | dict[str, "JSON"]
# Generic type alias
type Stack[T] = list[T]
type Pair[T, U] = tuple[T, U]
type Callback[T] = callable[[T], None]
# Use them
def push[T](stack: Stack[T], item: T) -> Stack[T]:
return [*stack, item]
numbers: Stack[int] = [1, 2, 3]
result = push(numbers, 4)
Generische Klassen und Funktionen (Python 3.12+)
# Old TypeVar syntax (still valid)
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T | None:
return items[0] if items else None
# New syntax (PEP 695)
def first[T](items: list[T]) -> T | None:
return items[0] if items else None
def map_list[T, U](items: list[T], fn: callable[[T], U]) -> list[U]:
return [fn(item) for item in items]
class Stack[T]:
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
if not self._items:
raise IndexError("Stack is empty")
return self._items.pop()
def peek(self) -> T | None:
return self._items[-1] if self._items else None
def __len__(self) -> int:
return len(self._items)
# Usage
stack: Stack[str] = Stack()
stack.push("hello")
stack.push("world")
print(stack.pop()) # "world"
# Constrained TypeVar
def sort_items[T: (int, str, float)](items: list[T]) -> list[T]:
return sorted(items)
TypeVar mit Standard (PEP 696 – Python 3.13+)
from typing import TypeVar
# TypeVar with default
T = TypeVar("T", default=str)
class Container[T = str]:
def __init__(self, value: T) -> None:
self.value = value
def get(self) -> T:
return self.value
# T defaults to str if not specified
c1: Container = Container("hello") # T = str (default)
c2: Container[int] = Container(42) # T = int (explicit)
Protokolle – Strukturelle Subtypisierung
from typing import Protocol, runtime_checkable
from collections.abc import Sequence
@runtime_checkable
class Sizeable(Protocol):
def __len__(self) -> int: ...
@runtime_checkable
class Comparable[T](Protocol):
def __lt__(self, other: T) -> bool: ...
def __le__(self, other: T) -> bool: ...
def __gt__(self, other: T) -> bool: ...
def __ge__(self, other: T) -> bool: ...
# Any class with __len__ satisfies Sizeable — no inheritance needed
def count_items(collection: Sizeable) -> int:
return len(collection)
print(count_items([1, 2, 3])) # works: list has __len__
print(count_items({"a": 1})) # works: dict has __len__
print(count_items("hello")) # works: str has __len__
# isinstance check (requires @runtime_checkable)
print(isinstance([1, 2], Sizeable)) # True
# Protocol with methods
class Serializable(Protocol):
def to_json(self) -> str: ...
def to_dict(self) -> dict: ...
@classmethod
def from_json(cls, json_str: str) -> "Serializable": ...
Überladungen – Mehrere Signaturen
from typing import overload
@overload
def process(x: int) -> int: ...
@overload
def process(x: str) -> str: ...
@overload
def process(x: list[int]) -> list[int]: ...
def process(x):
if isinstance(x, int):
return x * 2
elif isinstance(x, str):
return x.upper()
elif isinstance(x, list):
return [item * 2 for item in x]
# Type checker knows the return type based on input type
result_int: int = process(5) # type checker: int
result_str: str = process("hi") # type checker: str
result_list: list[int] = process([1, 2, 3]) # type checker: list[int]
TypedDict Erweiterte Muster
from typing import TypedDict, Required, NotRequired
# Partial TypedDict — some required, some optional
class UserCreate(TypedDict):
name: Required[str]
email: Required[str]
password: Required[str]
role: NotRequired[str] # optional
# Inheritance
class UserUpdate(TypedDict, total=False):
name: str
email: str
role: str
# Nested TypedDict
class Address(TypedDict):
street: str
city: str
country: str
zip_code: NotRequired[str]
class UserWithAddress(TypedDict):
id: int
name: str
address: Address
# Use in function signatures
def create_user(user: UserCreate) -> int:
return db.insert("users", user)
def update_user(user_id: int, updates: UserUpdate) -> bool:
return db.update("users", user_id, updates)
ParamSpec – Typsichere Dekoratoren
from typing import ParamSpec, TypeVar, Callable
import functools
P = ParamSpec("P")
R = TypeVar("R")
def retry(max_attempts: int = 3):
def decorator(func: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
raise RuntimeError("unreachable")
return wrapper
return decorator
@retry(max_attempts=3)
def fetch_data(url: str, timeout: int = 30) -> dict:
return requests.get(url, timeout=timeout).json()
# Type checker knows: fetch_data(url: str, timeout: int = 30) -> dict
# The decorator is fully transparent to the type system
Das Typsystem von Python im Jahr 2026 mit der neuen PEP 695-Syntax ist deutlich lesbarer als die TypeVar-basierte Syntax. Die Schlüsselmuster: Verwenden Sie Protocols für strukturelle Typisierung, ParamSpec für typsichere Dekoratoren, TypedDict für typisierte Wörterbücher, @overload für mehrere Signaturen und die neue Typanweisung für saubere Aliase. Führen Sie pyright –strict oder mypy –strict aus, um diese Muster durchzusetzen.
🔗 Share this article
✍️ Leave a Comment