What is PEP 695?
PEP 695: Type Parameter Syntax (Python 3.12) builds generics into the language. class Box[T]: declares a generic class, def first[T](xs: list[T]) -> T: a generic function, and type Maybe[T] = T | None a type alias, with no TypeVar imports and no Generic base class.
Generics had been spelled through the typing module since PEP 484 introduced type hints. PEP 695 gives them syntax.
What does the new syntax replace?
The before and after for a generic class:
# Before 3.12
from typing import Generic, TypeVar
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, item: T) -> None:
self.item = item
# 3.12+
class Box[T]:
def __init__(self, item: T) -> None:
self.item = itemThe new form fixes more than verbosity. The old T = TypeVar("T") was a module-level variable with no defined scope, shared across every function that referenced it; PEP 695 type parameters are scoped to the class or function that declares them. Variance is now inferred from how a parameter is used instead of declared with covariant=True flags. The declared parameters are introspectable at runtime via Box.__type_params__.
Constraints use a bound after a colon: def largest[T: (int, float)](xs: list[T]) -> T: or class Sorted[T: Comparable]:.
How does the type statement work?
The type statement replaces Alias = Union[...] assignments and the TypeAlias annotation:
type Maybe[T] = T | NoneThis creates a typing.TypeAliasType object whose value is evaluated lazily: the right-hand side isn’t computed until something asks for Maybe.__value__. Forward references in an alias therefore work without quotes, the same idea PEP 649 later applied to all annotations.
Can you use it yet?
The deciding question is your minimum supported Python, because PEP 695 is grammar. typing_extensions can backport objects like ParamSpec, but it cannot make the 3.9 parser accept class Box[T]:. Code that must run on 3.11 or older keeps the TypeVar spelling.
For code that’s 3.12-only, support is solid across the toolchain:
- mypy, Pyright, and ty all check the new syntax, including inferring
intfromfirst([1, 2, 3])and rejectingBox[int]("not an int"). Pyright supported it at the 3.12 release; mypy’s support arrived across several releases (mypy issue #15238 tracked the rollout). - Ruff migrates old code automatically:
UP040rewritesTypeAliasassignments totypestatements, andUP046/UP047rewriteGeneric[T]classes andTypeVarfunctions to the bracket syntax.
Python 3.13 extended the syntax with type parameter defaults (PEP 696, default values like class Box[T = str]:).
Learn More
- PEP 695: Type Parameter Syntax
- Type parameter lists in the language reference
- Ruff rule UP040: non-pep695-type-alias
- What is PEP 604? covers the matching modernization for unions
- What is PEP 612? covers
ParamSpec, which this syntax declares inline as[**P]