Skip to main content
🧬
PEP 695 · Speedrun · Python 3.12 (2023)

PEP 695 in 90 seconds — generic types without TypeVar

Generic functions and classes, finally readable.

The old way to write a generic function in Python: ```python from typing import TypeVar T = TypeVar("T") def first(items: list[T]) -> T: return items[0] ``` Three places to look. An import, a module-scoped binding, then the actual signature.

PEP 695 (Python 3.12, 2023) put the type parameter where it belongs — inline: ```python def first[T](items: list[T]) -> T: return items[0] ``` No import. No module-scoped TypeVar. The `[T]` between the function name and the params declares the type variable for THIS function only. Generic classes get the same treatment: `class Stack[T]:`. Runtime behaviour is identical to the old way — this is a syntax win, not a feature change. But it's a big enough syntax win that mypy + pyright + Pydantic v2 all support both.

Before — old typing.TypeVar dance
from typing import TypeVar

T = TypeVar("T")

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

class Stack(Generic[T]):
    def __init__(self) -> None:
        self._data: list[T] = []
After — PEP 695 inline syntax
# No import needed
def first[T](items: list[T]) -> T:
    return items[0]

class Stack[T]:
    def __init__(self) -> None:
        self._data: list[T] = []

🎯 Predict the output

What does this print? (PEP 695 is syntax — runtime behaviour is identical to TypeVar.)

def last[T](items: list[T]) -> T:
    return items[-1]

print(last([1, 2, 3]))
print(last(["a", "b", "c"]))
Modern Python idioms → Senior track

Or speedrun another PEP

PEP 572Assignment as an expression — 3 lines become 1.
PEP 572 in 90 seconds — the walrus that ate your for-loop
PEP 484Python stays dynamic — but you can opt into typing.
PEP 484 in 90 seconds — type hints, without TypeScript-envy
PEP 622Switch statements are a 5% feature. Pattern matching is the 95%.
PEP 622 in 90 seconds — match / case isn't just a switch