TypeVar and Generic Functions
Overview
A `TypeVar` creates a generic function that adapts its return type based on the input type. By using a placeholder like `T`, you tell the type checker to track the specific type passing through the function. def get_first(items: list[T]) -> T: return items[0] Whatever type `T` is in the input list, that exact same type `T` will be returned. If you pass a `list[int]`, `T` becomes `int`, preserving full IDE autocomplete. # Python 3.12+ syntax def get_first[T](items: list[T]) -> T: return items[0] In Python 3.12+, you can skip the `TypeVar` import and declare the generic parameter directly in square brackets. def choose[T](a: T, b: T) -> T: return a # Trap: Conflicting types for the same TypeVar val = choose(1, "apple") When multiple arguments share the same `TypeVar`, passing different types forces the type checker to resolve a common base class or union. This can accidentally weaken your type safety if you expected strict matching.
Without `TypeVar`, you'd have to use `Any` or `Union` for a function that works on many types, but that destroys autocomplete. `TypeVar` preserves the exact type information passing *through* the function.
Where used: Utility functions like `first_or_default`, Decorators that preserve the decorated function's signature
Why learn this
- Writing utility functions that work for any type without breaking the IDE's autocomplete.
- Understanding modern Python 3.12+ generic syntax.
- Reading framework code that uses `TypeVar` heavily to maintain type safety.
Code walkthrough
from typing import TypeVar
T = TypeVar('T')
def get_last(items: list[T]) -> T:
return items[-1]
number = get_last([1, 2, 3])
text = get_last(['a', 'b', 'c'])
print(f'{number}, {text}')
Focus: def get_last(items: list[T]) -> T:
Aha moment
from typing import TypeVar
T = TypeVar('T')
print(type(T))
Prediction: What does this print?
Common guess: It prints something like `<class 'type'>` or it fails because `T` isn't a real class.
It prints `<class 'typing.TypeVar'>`. `TypeVar` is just an object created at runtime to hold metadata for static analysis tools; it has no magical behavior when the program executes.
Common mistakes
- Using Any instead of TypeVar: If `def get_first(items: list[Any]) -> Any:` is used, the type checker forgets the actual type of the items. Calling `.upper()` on the result won't be checked. `TypeVar` preserves the specific type.
- Forgetting that T is bound per-call: `T` is not a specific type like `int`; it's a placeholder that resolves to a concrete type *when the function is called*. It can be `int` on line 10 and `str` on line 12.
- Passing mismatched types to the same TypeVar: If a function is defined as `def add[T](a: T, b: T) -> T:`, calling `add(5, "five")` forces the type checker to compromise. Depending on your type checker settings, it will either throw an error or infer a loose type like `object`, losing strict type safety.
Glossary
- generic function
- A function designed to work with various data types while still keeping track of the specific type being used. Example: `def get_first[T](items: list[T]) -> T:`.
- placeholder
- A temporary symbol or variable (like `T`) that stands in for an actual, concrete data type until the function is called. Example: `T = TypeVar('T')`.
Recall questions
- What is the primary advantage of using `TypeVar` over `Any`?
- How does the generic type syntax for functions change in Python 3.12+?
Understanding checks
Why will a static type checker complain about this function?
The return type says it will return a list of `T` (the exact type passed in), but the function body forces the items to be strings.
The promise of `T` is that the output type matches the input type. If the input is an `int`, it promises a `list[int]`, but actually returns a `list[str]`.
In `def identity[T](value: T) -> T:`, what type is `T` when you call `identity(42)`?
`T` resolves to `int`.
The type checker infers the concrete type of `T` based on the argument provided at the call site.
Practice tasks
Use a `TypeVar`
Given this working code, replace the `Any` type hints with a `TypeVar` named `T` so the type checker knows the return type matches the input type.
Continue learning
Previous: Generic Types