Back to Blog
Python

Python Generic Function Syntax: TypeVar, Generic, and PEP 695

python generic function syntax: Learn Python generic function syntax with TypeVar, Generic, and PEP 695. See how to write reusable, type-safe functions with modern typ...

Python typingTypeVarGenericType HintsPEP 695
Python generic function syntax diagram showing type variables flowing from input to output in a function signature

Generic functions let you write a single implementation that works with multiple types while preserving type information. The core python generic function syntax relies on TypeVar and the typing module. Here is the minimal form:

from typing import TypeVar T = TypeVar('T') def first(seq: list[T]) -> T: return seq[0]

The function first accepts a list of any type T and returns an element of that same type. Without the TypeVar, you would have to use Any or overload the function, losing the connection between input and output types.

Defining Type Variables with TypeVar

TypeVar is the building block for generics. Its basic syntax is TypeVar('T'), where the string is the name used for error messages and introspection. You can also constrain the types that are allowed:

Number = TypeVar('Number', int, float) def add(a: Number, b: Number) -> Number: return a + b

Here add only accepts int or float arguments, and the return type matches the input type. Constraints are checked by static type checkers, not at runtime.

For more flexible bounds, use bound:

from typing import TypeVar class Shape: def area(self) -> float: ... S = TypeVar('S', bound=Shape) def print_area(shape: S) -> None: print(shape.area())

The bound parameter restricts S to Shape or any subclass. This is useful when you need to call methods on the generic value.

Using TypeVar in Function Signatures

Type variables can appear in multiple positions, including arguments and return types. For example, a function that maps a list of one type to a list of another:

from typing import TypeVar, Callable A = TypeVar('A') B = TypeVar('B') def map_list(fn: Callable[[A], B], items: list[A]) -> list[B]: return [fn(item) for item in items]

Here A and B are independent type variables. The function accepts a callable that takes A and returns B, plus a list of A, and returns a list of B. This preserves the relationship between the callback and the list elements.

You can also use a single type variable to enforce that two arguments share the same type:

def pair(left: T, right: T) -> tuple[T, T]: return (left, right)

Static checkers will reject calls like pair(1, 'a') because T cannot be both int and str.

Generic Classes with Generic

For classes, the Generic base class provides the same type-variable mechanics. The syntax is:

from typing import Generic, TypeVar T = TypeVar('T') class Stack(Generic[T]): 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()

The class Stack is parameterized by T. When you instantiate Stack[int], the methods are typed accordingly. This is the standard approach for reusable data structures.

The New PEP 695 Syntax for Generic Functions

Python 3.12 introduced a more concise syntax for generics, defined in PEP 695. Instead of declaring a TypeVar separately, you can write the type variable directly in the function signature:

def first[T](seq: list[T]) -> T: return seq[0]

Similarly, classes can be declared with a type parameter directly:

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: return self._items.pop()

This syntax is cleaner and avoids the extra TypeVar assignment. It also supports bounds and constraints inline:

def print_area[S: Shape](shape: S) -> None: print(shape.area())

PEP 695 is backward compatible: code written with the old syntax still works, and type checkers like mypy and pyright support both forms.

Common Mistakes and Pitfalls

A frequent error is using Any instead of a type variable. Any disables type checking entirely, so you lose the guarantee that the return type matches the argument type. Another mistake is forgetting to use TypeVar in all positions, which can cause the type checker to infer Any.

Another pitfall is reusing the same type variable for unrelated parameters. For example:

def bad(a: T, b: T) -> T: return a

This forces a and b to have the same type, which may not be intended. Use separate type variables when the types are independent.

Finally, note that type variables are erased at runtime. The TypeVar object exists only for static analysis; it has no effect on function behavior. Attempting to inspect the type at runtime with isinstance or type will not work as expected.

Runtime Behavior and Performance

Generic functions do not incur runtime overhead. The type annotations are stripped or ignored during execution, and the function behaves exactly as if the annotations were absent. This means you can use generics freely without worrying about performance penalties.

The only runtime cost is the creation of the TypeVar object itself, which happens once at module load. In PEP 695, even that is avoided because the type parameter is part of the function's __type_params__ attribute, but it still does not affect call performance.

If you need to preserve type information at runtime, you must use typing.get_type_hints() or inspect.signature(), but that is rarely necessary in production code.

Compatibility and Tooling

The old TypeVar syntax works in Python 3.5 and later, making it the safe choice for libraries that support older versions. PEP 695 requires Python 3.12 or newer. If you are writing a library, consider supporting both forms or using the old syntax for maximum compatibility.

Static type checkers handle both syntaxes. Mypy added support for PEP 695 in version 1.4, and Pyright supports it from version 1.1.310. If you use an older checker, you may need to stick with the classic syntax.

When mixing both styles in one codebase, be consistent. The PEP 695 syntax is more readable for new code, but the classic syntax is more explicit when you need to reuse a type variable across multiple functions or classes. Choose the style that matches your project's Python version and tooling constraints.

python generic function syntax: Practical Usage and Code Exa | RYUSLOG DEV