Modern Python type hints can express generic functions and classes — code that works with a type parameter, checked consistently across a single call — using increasingly concise syntax across recent versions.
from typing import TypeVar, Generic
T = TypeVar("T") # a type variable — pre-3.12 way to write generics
def first_item(items: list[T]) -> T: # whatever type the list holds, that's the return type
return items[0]
class Stack(Generic[T]): # a generic class — pre-3.12 syntax
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
int_stack: Stack[int] = Stack() # a Stack specifically of ints
int_stack.push(5)
# Python 3.12+ new, cleaner generic syntax — no TypeVar import needed:
def first_item_new[T](items: list[T]) -> T:
return items[0]
class Stack312[T]:
def __init__(self) -> None:
self._items: list[T] = []
A generic function like first_item says "I accept a list of some type T, and I return that same type T" — a type checker can then verify that first_item([1, 2, 3]) returns an int, and first_item(["a", "b"]) returns a str, without needing a separate hint-writing pass for every possible type.
Python 3.12 introduced new, built-in syntax for generics (def func[T](...), class Stack[T]:) that eliminates the need to import and declare TypeVar separately — a meaningful readability improvement, though the older TypeVar/Generic style (shown above) remains necessary for code that needs to support Python versions before 3.12, and is still extremely common to encounter when reading existing codebases.