From 9cc25728821f4da2001c9144162cea6209b8c53a Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 28 Sep 2025 13:13:16 -0700 Subject: [PATCH 1/7] Updated the typing spec to use modern (PEP 695) generics syntax --- docs/spec/aliases.rst | 15 +-- docs/spec/annotations.rst | 6 +- docs/spec/class-compat.rst | 7 +- docs/spec/constructors.rst | 8 +- docs/spec/dataclasses.rst | 10 +- docs/spec/generics.rst | 254 +++++++++++++++--------------------- docs/spec/literal.rst | 36 +++-- docs/spec/narrowing.rst | 8 +- docs/spec/overload.rst | 32 ++--- docs/spec/protocol.rst | 34 +++-- docs/spec/qualifiers.rst | 7 +- docs/spec/special-types.rst | 3 +- 12 files changed, 173 insertions(+), 247 deletions(-) diff --git a/docs/spec/aliases.rst b/docs/spec/aliases.rst index a965eca41..cc5e70b55 100644 --- a/docs/spec/aliases.rst +++ b/docs/spec/aliases.rst @@ -34,29 +34,24 @@ Type aliases may be as complex as type hints in annotations -- anything that is acceptable as a type hint is acceptable in a type alias:: - from typing import TypeVar from collections.abc import Iterable - T = TypeVar('T', bound=float) - Vector = Iterable[tuple[T, T]] + type Vector[T: float] = Iterable[tuple[T, T]] - def inproduct(v: Vector[T]) -> T: + def inproduct[T: float](v: Vector[T]) -> T: return sum(x*y for x, y in v) - def dilate(v: Vector[T], scale: T) -> Vector[T]: + def dilate[T: float](v: Vector[T], scale: T) -> Vector[T]: return ((x * scale, y * scale) for x, y in v) vec: Vector[float] = [] This is equivalent to:: - from typing import TypeVar from collections.abc import Iterable - T = TypeVar('T', bound=float) - - def inproduct(v: Iterable[tuple[T, T]]) -> T: + def inproduct[T: float](v: Iterable[tuple[T, T]]) -> T: return sum(x*y for x, y in v) - def dilate(v: Iterable[tuple[T, T]], scale: T) -> Iterable[tuple[T, T]]: + def dilate[T: float](v: Iterable[tuple[T, T]], scale: T) -> Iterable[tuple[T, T]]: return ((x * scale, y * scale) for x, y in v) vec: Iterable[tuple[float, float]] = [] diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst index 7f8f599e2..8cc79f4ab 100644 --- a/docs/spec/annotations.rst +++ b/docs/spec/annotations.rst @@ -366,9 +366,8 @@ In addition, the first argument in an instance method can be annotated with a type variable. In this case the return type may use the same type variable, thus making that method a generic function. For example:: - T = TypeVar('T', bound='Copyable') class Copyable: - def copy(self: T) -> T: + def copy[T: 'Copyable'](self: T) -> T: # return a copy of self class C(Copyable): ... @@ -378,10 +377,9 @@ type variable, thus making that method a generic function. For example:: The same applies to class methods using ``type[]`` in an annotation of the first argument:: - T = TypeVar('T', bound='C') class C: @classmethod - def factory(cls: type[T]) -> T: + def factory[T: 'C'](cls: type[T]) -> T: # make a new instance of cls class D(C): ... diff --git a/docs/spec/class-compat.rst b/docs/spec/class-compat.rst index 2984c5a3c..fe7df968e 100644 --- a/docs/spec/class-compat.rst +++ b/docs/spec/class-compat.rst @@ -101,11 +101,8 @@ For example, annotating the discussed class:: As a matter of convenience (and convention), instance variables can be annotated in ``__init__`` or other methods, rather than in the class:: - from typing import Generic, TypeVar - T = TypeVar('T') - - class Box(Generic[T]): - def __init__(self, content): + class Box[T]: + def __init__(self, content: T): self.content: T = content ``ClassVar`` cannot be used as a qualifier for a :ref:`TypedDict ` diff --git a/docs/spec/constructors.rst b/docs/spec/constructors.rst index b8591ba12..c95ecc488 100644 --- a/docs/spec/constructors.rst +++ b/docs/spec/constructors.rst @@ -318,16 +318,14 @@ these method evaluations, they should take on their default values. :: - T1 = TypeVar("T1") - T2 = TypeVar("T2") - T3 = TypeVar("T3", default=str) + from typing import Any, Self, assert_type - class MyClass1(Generic[T1, T2]): + class MyClass1[T1, T2]: def __new__(cls, x: T1) -> Self: ... assert_type(MyClass1(1), MyClass1[int, Any]) - class MyClass2(Generic[T1, T3]): + class MyClass2[T1, T3 = str]: def __new__(cls, x: T1) -> Self: ... assert_type(MyClass2(1), MyClass2[int, str]) diff --git a/docs/spec/dataclasses.rst b/docs/spec/dataclasses.rst index 92d53d227..e2ad0e655 100644 --- a/docs/spec/dataclasses.rst +++ b/docs/spec/dataclasses.rst @@ -72,12 +72,10 @@ Decorator function example .. code-block:: python - _T = TypeVar("_T") - # The ``create_model`` decorator is defined by a library. # This could be in a type stub or inline. @typing.dataclass_transform() - def create_model(cls: Type[_T]) -> Type[_T]: + def create_model[T](cls: type[T]) -> type[T]: cls.__init__ = ... cls.__eq__ = ... cls.__ne__ = ... @@ -148,9 +146,7 @@ customization of default behaviors: .. code-block:: python - _T = TypeVar("_T") - - def dataclass_transform( + def dataclass_transform[T]( *, eq_default: bool = True, order_default: bool = False, @@ -158,7 +154,7 @@ customization of default behaviors: frozen_default: bool = False, field_specifiers: tuple[type | Callable[..., Any], ...] = (), **kwargs: Any, - ) -> Callable[[_T], _T]: ... + ) -> Callable[[T], T]: ... * ``eq_default`` indicates whether the ``eq`` parameter is assumed to be True or False if it is omitted by the caller. If not specified, diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst index 4f4150c44..caca2392e 100644 --- a/docs/spec/generics.rst +++ b/docs/spec/generics.rst @@ -210,22 +210,15 @@ defined class is generic if you subclass one or more other generic classes and specify type variables for their parameters. See :ref:`generic-base-classes` for details. -You can use multiple inheritance with ``Generic``:: +You can use multiple inheritance with generic classes:: - from typing import TypeVar, Generic - from collections.abc import Sized, Iterable, Container - - T = TypeVar('T') + from collections.abc import Container, Iterable, Sized - class LinkedList(Sized, Generic[T]): + class LinkedList[T](Sized): ... - K = TypeVar('K') - V = TypeVar('V') - - class MyMapping(Iterable[tuple[K, V]], - Container[tuple[K, V]], - Generic[K, V]): + class MyMapping[K, V](Iterable[tuple[K, V]], + Container[tuple[K, V]]): ... Subclassing a generic class without specifying type parameters assumes @@ -245,8 +238,9 @@ Generic metaclasses are not supported. Scoping rules for type variables -------------------------------- -Type variables follow normal name resolution rules. -However, there are some special cases in the static typechecking context: +When using the generic class syntax introduced in Python 3.12, the location of +its declaration defines its scope. When using the older syntax, the scoping +rules are more subtle and complex. * A type variable used in a generic function could be inferred to represent different types in the same code block. Example:: @@ -344,13 +338,9 @@ Instantiating generic classes and type erasure ---------------------------------------------- User-defined generic classes can be instantiated. Suppose we write -a ``Node`` class inheriting from ``Generic[T]``:: +a ``Node`` class using the new generic class syntax:: - from typing import TypeVar, Generic - - T = TypeVar('T') - - class Node(Generic[T]): + class Node[T]: ... To create ``Node`` instances you call ``Node()`` just as for a regular @@ -362,12 +352,8 @@ corresponding argument value is passed, the type of the corresponding argument(s) is substituted. Otherwise, the default value for the type parameter (or ``Any``, if no default is provided) is assumed. Example:: - from typing import TypeVar, Generic - - T = TypeVar('T') - - class Node(Generic[T]): - x: T # Instance attribute (see below) + class Node[T]: + x: T # Instance attribute (see below) def __init__(self, label: T | None = None) -> None: ... @@ -665,8 +651,18 @@ ParamSpec Declaration """""""""""" -A parameter specification variable is defined in a similar manner to how a -normal type variable is defined with ``typing.TypeVar``. +In Python 3.12 and newer, a parameter specification variable can be introduced +inline by prefixing its name with ``**`` inside a generic parameter list +(for a function, class, or type alias). + +.. code-block:: + + def decorator[**P](func: Callable[P, int]) -> Callable[P, str]: ... + + class CallbackWrapper[T, **P]: + callback: Callable[P, T] + +Prior to 3.12, the ``ParamSpec`` constructor can be used:: .. code-block:: @@ -702,8 +698,8 @@ parameter specification variable (``Callable[Concatenate[int, P], int]``\ ). parameter_specification_variable "]" -where ``parameter_specification_variable`` is a ``typing.ParamSpec`` variable, -declared in the manner as defined above, and ``concatenate`` is +where ``parameter_specification_variable`` is introduced either inline (using +``**``) or via ``typing.ParamSpec`` as shown above, and ``concatenate`` is ``typing.Concatenate``. As before, ``parameters_expression``\ s by themselves are not acceptable in @@ -711,53 +707,45 @@ places where a type is expected .. code-block:: - def foo(x: P) -> P: ... # Rejected - def foo(x: Concatenate[int, P]) -> int: ... # Rejected - def foo(x: list[P]) -> None: ... # Rejected - def foo(x: Callable[[int, str], P]) -> None: ... # Rejected + def foo[**P](x: P) -> P: ... # Rejected + def foo[**P](x: Concatenate[int, P]) -> int: ... # Rejected + def foo[**P](x: list[P]) -> None: ... # Rejected + def foo[**P](x: Callable[[int, str], P]) -> None: ... # Rejected User-Defined Generic Classes """""""""""""""""""""""""""" -Just as defining a class as inheriting from ``Generic[T]`` makes a class generic -for a single parameter (when ``T`` is a ``TypeVar``\ ), defining a class as -inheriting from ``Generic[P]`` makes a class generic on -``parameters_expression``\ s (when ``P`` is a ``ParamSpec``). +Just as defining a class with ``class C[T]: ...`` makes that class generic over +the type parameter ``T``, adding ``**P`` makes it generic over a parameter +specification. .. code-block:: - T = TypeVar("T") - P_2 = ParamSpec("P_2") - - class X(Generic[T, P]): - f: Callable[P, int] - x: T - - def f(x: X[int, P_2]) -> str: ... # Accepted - def f(x: X[int, Concatenate[int, P_2]]) -> str: ... # Accepted - def f(x: X[int, [int, bool]]) -> str: ... # Accepted - def f(x: X[int, ...]) -> str: ... # Accepted - def f(x: X[int, int]) -> str: ... # Rejected + from collections.abc import Callable + from typing import Concatenate, ParamSpec -Or, equivalently, using the built-in syntax for generics in Python 3.12 -and higher:: + class X[T, **P]: + f: Callable[P, int] + x: T - class X[T, **P]: - f: Callable[P, int] - x: T + def accept_params[**P](x: X[int, P]) -> str: ... # Accepted + def accept_concatenate[**P](x: X[int, Concatenate[int, P]]) -> str: ... # Accepted + def accept_concrete(x: X[int, [int, bool]]) -> str: ... # Accepted + def accept_unspecified(x: X[int, ...]) -> str: ... # Accepted + def reject_bad(x: X[int, int]) -> str: ... # Rejected By the rules defined above, spelling a concrete instance of a class generic with respect to only a single ``ParamSpec`` would require unsightly double -brackets. For aesthetic purposes we allow these to be omitted. +brackets. For aesthetic purposes we allow these to be omitted. .. code-block:: - class Z(Generic[P]): - f: Callable[P, int] + class Z[**P]: + f: Callable[P, int] - def f(x: Z[[int, str, bool]]) -> str: ... # Accepted - def f(x: Z[int, str, bool]) -> str: ... # Equivalent + def accept_list(x: Z[[int, str, bool]]) -> str: ... # Accepted + def accept_flat(x: Z[int, str, bool]) -> str: ... # Equivalent # Both Z[[int, str, bool]] and Z[int, str, bool] express this: class Z_instantiated: @@ -772,7 +760,7 @@ evaluating ones with ``TypeVar``\ s. .. code-block:: - def changes_return_type_to_str(x: Callable[P, int]) -> Callable[P, str]: ... + def changes_return_type_to_str[**P](x: Callable[P, int]) -> Callable[P, str]: ... def returns_int(a: str, b: bool) -> int: ... @@ -795,9 +783,7 @@ but is not obligated to do so. .. code-block:: - P = ParamSpec("P") - - def foo(x: Callable[P, int], y: Callable[P, int]) -> Callable[P, bool]: ... + def foo[**P](x: Callable[P, int], y: Callable[P, int]) -> Callable[P, bool]: ... def x_y(x: int, y: str) -> int: ... def y_x(y: int, x: str) -> int: ... @@ -820,9 +806,7 @@ evaluated in the same way. .. code-block:: - U = TypeVar("U") - - class Y(Generic[U, P]): + class Y[U, **P]: f: Callable[P, str] prop: U @@ -844,15 +828,15 @@ transform a finite number of parameters of a callable. def bar(x: int, *args: bool) -> int: ... - def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ... + def add[**P](x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ... add(bar) # Should return (a: str, /, x: int, *args: bool) -> bool - def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ... + def remove[**P](x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ... remove(bar) # Should return (*args: bool) -> bool - def transform( + def transform[**P]( x: Callable[Concatenate[int, P], int] ) -> Callable[Concatenate[str, P], bool]: ... @@ -865,7 +849,7 @@ their first position with a ``X`` can satisfy .. code-block:: - def expects_int_first(x: Callable[Concatenate[int, P], int]) -> None: ... + def expects_int_first[**P](x: Callable[Concatenate[int, P], int]) -> None: ... @expects_int_first # Rejected def one(x: str) -> int: ... @@ -907,7 +891,7 @@ These "properties" can only be used as the annotated types for .. code-block:: - def puts_p_into_scope(f: Callable[P, int]) -> None: + def p_in_scope[**P](f: Callable[P, int]) -> None: def inner(*args: P.args, **kwargs: P.kwargs) -> None: # Accepted pass @@ -918,7 +902,7 @@ These "properties" can only be used as the annotated types for def misplaced(x: P.args) -> None: # Rejected pass - def out_of_scope(*args: P.args, **kwargs: P.kwargs) -> None: # Rejected + def p_not_in_scope(*args: P.args, **kwargs: P.kwargs) -> None: # Rejected pass @@ -931,7 +915,7 @@ together, so that our usage is valid for all possible partitions. .. code-block:: - def puts_p_into_scope(f: Callable[P, int]) -> None: + def p_in_scope[**P](f: Callable[P, int]) -> None: stored_args: P.args # Rejected @@ -971,7 +955,7 @@ parameter preserving decorators. .. code-block:: - def decorator(f: Callable[P, int]) -> Callable[P, None]: + def decorator[**P](f: Callable[P, int]) -> Callable[P, None]: def foo(*args: P.args, **kwargs: P.kwargs) -> None: @@ -996,7 +980,7 @@ To extend this to include ``Concatenate``, we declare the following properties: .. code-block:: - def add(f: Callable[P, int]) -> Callable[Concatenate[str, P], None]: + def add[**P](f: Callable[P, int]) -> Callable[Concatenate[str, P], None]: def foo(s: str, *args: P.args, **kwargs: P.kwargs) -> None: # Accepted pass @@ -1007,7 +991,7 @@ To extend this to include ``Concatenate``, we declare the following properties: return foo # Accepted - def remove(f: Callable[Concatenate[int, P], int]) -> Callable[P, None]: + def remove[**P](f: Callable[Concatenate[int, P], int]) -> Callable[P, None]: def foo(*args: P.args, **kwargs: P.kwargs) -> None: f(1, *args, **kwargs) # Accepted @@ -1024,7 +1008,7 @@ these parameters can not be addressed via a named argument: .. code-block:: - def outer(f: Callable[P, None]) -> Callable[P, None]: + def outer[**P](f: Callable[P, None]) -> Callable[P, None]: def foo(x: int, *args: P.args, **kwargs: P.kwargs) -> None: f(*args, **kwargs) @@ -1061,7 +1045,7 @@ of that ``ParamSpec``. That allows us to spell things like this: .. code-block:: - def twice(f: Callable[P, int], *args: P.args, **kwargs: P.kwargs) -> int: + def twice[**P](f: Callable[P, int], *args: P.args, **kwargs: P.kwargs) -> int: return f(*args, **kwargs) + f(*args, **kwargs) The type of ``twice`` in the above example is @@ -1101,27 +1085,27 @@ In the same way that a normal type variable is a stand-in for a single type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as ``tuple[int, str]``. -Type variable tuples are created and used with: +In Python 3.12 and newer, type variable tuples can be introduced inline by prefixing +their name with ``*`` inside a generic parameter list. :: - from typing import TypeVarTuple + class Array[*Ts]: + ... - Ts = TypeVarTuple('Ts') + def pack[*Ts](*values: *Ts) -> tuple[*Ts]: + ... - class Array(Generic[*Ts]): - ... +Prior to 3.12, the ``TypeVarTuple`` constructor can be used. - def foo(*args: *Ts): - ... +:: -Or when using the built-in syntax for generics in Python 3.12 and higher:: + from typing import TypeVarTuple - class Array[*Ts]: - ... + Ts = TypeVarTuple('Ts') - def foo[*Ts](*args: *Ts): - ... + class Array(Generic[*Ts]): + ... Using Type Variable Tuples in Generic Classes """"""""""""""""""""""""""""""""""""""""""""" @@ -1131,9 +1115,9 @@ Type variable tuples behave like a number of individual type variables packed in :: - Shape = TypeVarTuple('Shape') + from typing import NewType - class Array(Generic[*Shape]): ... + class Array[*Shape]: ... Height = NewType('Height', int) Width = NewType('Width', int) @@ -1143,9 +1127,9 @@ The ``Shape`` type variable tuple here behaves like ``tuple[T1, T2]``, where ``T1`` and ``T2`` are type variables. To use these type variables as type parameters of ``Array``, we must *unpack* the type variable tuple using the star operator: ``*Shape``. The signature of ``Array`` then behaves -as if we had simply written ``class Array(Generic[T1, T2]): ...``. +as if we had simply written ``class Array[T1, T2]: ...``. -In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows +In contrast to ``class Array[T1, T2]``, however, ``class Array[*Shape]`` allows us to parameterize the class with an *arbitrary* number of type parameters. That is, in addition to being able to define rank-2 arrays such as ``Array[Height, Width]``, we could also define rank-3 arrays, rank-4 arrays, @@ -1167,7 +1151,7 @@ signatures and variable annotations: :: - class Array(Generic[*Shape]): + class Array[*Shape]: def __init__(self, shape: tuple[*Shape]): self._shape: tuple[*Shape] = shape @@ -1251,7 +1235,7 @@ Only a single type variable tuple may appear in a type parameter list: :: - class Array(Generic[*Ts1, *Ts2]): ... # Error + class Array[*Ts1, *Ts2]: ... # Error The reason is that multiple type variable tuples make it ambiguous which parameters get bound to which type variable tuple: :: @@ -1266,13 +1250,12 @@ prefixed and/or suffixed: :: - Shape = TypeVarTuple('Shape') Batch = NewType('Batch', int) Channels = NewType('Channels', int) - def add_batch_axis(x: Array[*Shape]) -> Array[Batch, *Shape]: ... - def del_batch_axis(x: Array[Batch, *Shape]) -> Array[*Shape]: ... - def add_batch_channels( + def add_batch_axis[*Shape](x: Array[*Shape]) -> Array[Batch, *Shape]: ... + def del_batch_axis[*Shape](x: Array[Batch, *Shape]) -> Array[*Shape]: ... + def add_batch_channels[*Shape]( x: Array[*Shape] ) -> Array[Batch, *Shape, Channels]: ... @@ -1286,10 +1269,7 @@ Normal ``TypeVar`` instances can also be prefixed and/or suffixed: :: - T = TypeVar('T') - Ts = TypeVarTuple('Ts') - - def prefix_tuple( + def prefix_tuple[T, *Ts]( x: T, y: tuple[*Ts] ) -> tuple[T, *Ts]: ... @@ -1560,8 +1540,8 @@ a similar way to regular type variables: :: - IntTuple = tuple[int, *Ts] - NamedArray = tuple[str, Array[*Ts]] + type IntTuple[*Ts] = tuple[int, *Ts] + type NamedArray[*Ts] = tuple[str, Array[*Ts]] IntTuple[float, bool] # Equivalent to tuple[int, float, bool] NamedArray[Height] # Equivalent to tuple[str, Array[Height]] @@ -1574,15 +1554,14 @@ or datatype: :: - Shape = TypeVarTuple('Shape') - DType = TypeVar('DType') - class Array(Generic[DType, *Shape]): + class Array[DType, *Shape]: + ... # E.g. Float32Array[Height, Width, Channels] - Float32Array = Array[np.float32, *Shape] + type Float32Array[*Shape] = Array[np.float32, *Shape] # E.g. Array1D[np.uint8] - Array1D = Array[DType, Any] + type Array1D[DType] = Array[DType, Any] If an explicitly empty type parameter list is given, the type variable tuple in the alias is set empty: @@ -1612,8 +1591,7 @@ Normal ``TypeVar`` instances can also be used in such aliases: :: - T = TypeVar('T') - Foo = tuple[T, *Ts] + type Foo[T, *Ts] = tuple[T, *Ts] # T bound to str, Ts to tuple[int] Foo[str, int] @@ -1639,11 +1617,8 @@ First, type arguments to generic aliases can be variadic. For example, a :: - Ts1 = TypeVarTuple('Ts1') - Ts2 = TypeVarTuple('Ts2') - - IntTuple = tuple[int, *Ts1] - IntFloatTuple = IntTuple[float, *Ts2] # Valid + type IntTuple[*Ts1] = tuple[int, *Ts1] + type IntFloatTuple[*Ts2] = IntTuple[float, *Ts2] # Valid Here, ``*Ts1`` in the ``IntTuple`` alias is bound to ``tuple[float, *Ts2]``, resulting in an alias ``IntFloatTuple`` equivalent to @@ -1669,9 +1644,7 @@ themselves variadic. For example: :: - T = TypeVar('T') - - IntTuple = tuple[int, T] + type IntTuple[T] = tuple[int, T] IntTuple[str] # Valid IntTuple[*Ts] # NOT valid @@ -1792,11 +1765,9 @@ true of ``TypeVarTuple``\s in the argument list: :: - Ts1 = TypeVarTuple('Ts1') - Ts2 = TypeVarTuple('Ts2') - - Camelot = tuple[T, *Ts1] - Camelot[*Ts2] # NOT valid + type Camelot[T, *Ts] = tuple[T, *Ts] + Camelot[int, *tuple[str, ...]] # Valid + Camelot[*tuple[str, ...]] # NOT valid This is not possible because, unlike in the case of an unpacked arbitrary-length tuple, there is no way to 'peer inside' the ``TypeVarTuple`` to see what its @@ -1811,12 +1782,11 @@ overloads can be used with individual ``TypeVar`` instances in place of the type :: - Shape = TypeVarTuple('Shape') Axis1 = TypeVar('Axis1') Axis2 = TypeVar('Axis2') Axis3 = TypeVar('Axis3') - class Array(Generic[*Shape]): + class Array[*Shape]: @overload def transpose( @@ -1907,11 +1877,9 @@ literal "``...``" or another in-scope ``ParamSpec`` (see `Scoping Rules`_). :: - DefaultP = ParamSpec("DefaultP", default=[str, int]) - - class Foo(Generic[DefaultP]): ... + class Foo[**P = [str, int]]: ... - reveal_type(Foo) # type is type[Foo[DefaultP = [str, int]]] + reveal_type(Foo) # type is type[Foo[P = [str, int]]] reveal_type(Foo()) # type is Foo[[str, int]] reveal_type(Foo[[bool, bool]]()) # type is Foo[[bool, bool]] @@ -1924,11 +1892,9 @@ types or an unpacked, in-scope ``TypeVarTuple`` (see `Scoping Rules`_). :: - DefaultTs = TypeVarTuple("DefaultTs", default=Unpack[tuple[str, int]]) + class Foo[*Ts = *tuple[str, int]]: ... - class Foo(Generic[*DefaultTs]): ... - - reveal_type(Foo) # type is type[Foo[DefaultTs = *tuple[str, int]]] + reveal_type(Foo) # type is type[Foo[Ts = *tuple[str, int]]] reveal_type(Foo()) # type is Foo[str, int] reveal_type(Foo[int, bool]()) # type is Foo[int, bool] @@ -2135,10 +2101,7 @@ should be bound to the ``TypeVarTuple`` or the defaulted ``TypeVar``. :: - Ts = TypeVarTuple("Ts") - T = TypeVar("T", default=bool) - - class Foo(Generic[*Ts, T]): ... # Type checker error + class Foo[*Ts, T = bool]: ... # Type checker error # Could be reasonably interpreted as either Ts = (int, str, float), T = bool # or Ts = (int, str), T = float @@ -2150,13 +2113,10 @@ for the ``ParamSpec`` and one for the ``TypeVarTuple``. :: - Ts = TypeVarTuple("Ts") - P = ParamSpec("P", default=[float, bool]) - - class Foo(Generic[*Ts, P]): ... # Valid + class Foo[*Ts = *tuple[int, str], **P = [float, bool]]: ... # Valid - Foo[int, str] # Ts = (int, str), P = [float, bool] - Foo[int, str, [bytes]] # Ts = (int, str), P = [bytes] + Foo[int, str] # Ts = (int, str), P = [float, bool] + Foo[int, str, [bytes]] # Ts = (int, str), P = [bytes] Binding rules """"""""""""" diff --git a/docs/spec/literal.rst b/docs/spec/literal.rst index 1e820a63f..7b0015ca3 100644 --- a/docs/spec/literal.rst +++ b/docs/spec/literal.rst @@ -397,14 +397,10 @@ Literal types are types, and can be used anywhere a type is expected. For example, it is legal to parameterize generic functions or classes using Literal types:: - A = TypeVar('A', bound=int) - B = TypeVar('B', bound=int) - C = TypeVar('C', bound=int) - # A simplified definition for Matrix[row, column] - class Matrix(Generic[A, B]): + class Matrix[A: int, B: int]: def __add__(self, other: Matrix[A, B]) -> Matrix[A, B]: ... - def __matmul__(self, other: Matrix[B, C]) -> Matrix[A, C]: ... + def __matmul__[C: int](self, other: Matrix[B, C]) -> Matrix[A, C]: ... def transpose(self) -> Matrix[B, A]: ... foo: Matrix[Literal[2], Literal[3]] = Matrix(...) @@ -413,11 +409,14 @@ classes using Literal types:: baz = foo @ bar reveal_type(baz) # Revealed type is 'Matrix[Literal[2], Literal[7]]' -Similarly, it is legal to construct TypeVars with value restrictions +Similarly, it is legal to use type variables with value restrictions or bounds involving Literal types:: - T = TypeVar('T', Literal["a"], Literal["b"], Literal["c"]) - S = TypeVar('S', bound=Literal["foo"]) + def takes_letter[T: (Literal["a"], Literal["b"], Literal["c"])](value: T) -> T: + return value + + def takes_foo[S: Literal["foo"]](value: S) -> S: + return value ...although it is unclear when it would ever be useful to construct a TypeVar with a Literal upper bound. For example, the ``S`` TypeVar in @@ -562,7 +561,8 @@ Valid locations for ``LiteralString`` type_argument: List[LiteralString] - T = TypeVar("T", bound=LiteralString) + def enforce_literal[T: LiteralString](value: T) -> T: + return value It cannot be nested within unions of ``Literal`` types: @@ -733,18 +733,16 @@ Conditional statements and expressions work as expected: return result # OK -Interaction with TypeVars and Generics -"""""""""""""""""""""""""""""""""""""" +Interaction with Type Variables and Generics +"""""""""""""""""""""""""""""""""""""""""""" -TypeVars can be bound to ``LiteralString``: +Type variables can use ``LiteralString`` as an upper bound: :: - from typing import Literal, LiteralString, TypeVar - - TLiteral = TypeVar("TLiteral", bound=LiteralString) + from typing import Literal, LiteralString - def literal_identity(s: TLiteral) -> TLiteral: + def literal_identity[T: LiteralString](s: T) -> T: return s hello: Literal["hello"] = "hello" @@ -757,14 +755,14 @@ TypeVars can be bound to ``LiteralString``: s_error: str literal_identity(s_error) - # Error: Expected TLiteral (bound to LiteralString), got str. + # Error: Expected T (bound to LiteralString), got str. ``LiteralString`` can be used as a type argument for generic classes: :: - class Container(Generic[T]): + class Container[T]: def __init__(self, value: T) -> None: self.value = value diff --git a/docs/spec/narrowing.rst b/docs/spec/narrowing.rst index 27094e94c..6199aa092 100644 --- a/docs/spec/narrowing.rst +++ b/docs/spec/narrowing.rst @@ -34,9 +34,7 @@ User-defined type guards can be generic functions, as shown in this example: :: - _T = TypeVar("_T") - - def is_two_element_tuple(val: Tuple[_T, ...]) -> TypeGuard[tuple[_T, _T]]: + def is_two_element_tuple[T](val: tuple[T, ...]) -> TypeGuard[tuple[T, T]]: return len(val) == 2 def func(names: tuple[str, ...]): @@ -65,9 +63,7 @@ than one argument: return allow_empty return all(isinstance(x, str) for x in val) - _T = TypeVar("_T") - - def is_set_of(val: set[Any], type: type[_T]) -> TypeGuard[Set[_T]]: + def is_set_of[T](val: set[Any], type: type[T]) -> TypeGuard[set[T]]: return all(isinstance(x, type) for x in val) diff --git a/docs/spec/overload.rst b/docs/spec/overload.rst index 52982c54a..b1198871b 100644 --- a/docs/spec/overload.rst +++ b/docs/spec/overload.rst @@ -40,28 +40,28 @@ Another example where ``@overload`` comes in handy is the type of the builtin ``map()`` function, which takes a different number of arguments depending on the type of the callable:: - from typing import TypeVar, overload + from typing import overload from collections.abc import Callable, Iterable, Iterator - T1 = TypeVar('T1') - T2 = TypeVar('T2') - S = TypeVar('S') - @overload - def map(func: Callable[[T1], S], iter1: Iterable[T1]) -> Iterator[S]: ... + def map[T1, S](func: Callable[[T1], S], iter1: Iterable[T1]) -> Iterator[S]: ... @overload - def map(func: Callable[[T1, T2], S], - iter1: Iterable[T1], iter2: Iterable[T2]) -> Iterator[S]: ... + def map[T1, T2, S]( + func: Callable[[T1, T2], S], + iter1: Iterable[T1], iter2: Iterable[T2], + ) -> Iterator[S]: ... # ... and we could add more items to support more than two iterables Note that we could also easily add items to support ``map(None, ...)``:: @overload - def map(func: None, iter1: Iterable[T1]) -> Iterable[T1]: ... + def map[T1](func: None, iter1: Iterable[T1]) -> Iterable[T1]: ... @overload - def map(func: None, - iter1: Iterable[T1], - iter2: Iterable[T2]) -> Iterable[tuple[T1, T2]]: ... + def map[T1, T2]( + func: None, + iter1: Iterable[T1], + iter2: Iterable[T2], + ) -> Iterable[tuple[T1, T2]]: ... Uses of the ``@overload`` decorator as shown above are suitable for stub files. In regular modules, a series of ``@overload``-decorated @@ -91,11 +91,7 @@ A constrained ``TypeVar`` type can sometimes be used instead of using the ``@overload`` decorator. For example, the definitions of ``concat1`` and ``concat2`` in this stub file are equivalent:: - from typing import TypeVar - - AnyStr = TypeVar('AnyStr', str, bytes) - - def concat1(x: AnyStr, y: AnyStr) -> AnyStr: ... + def concat1[AnyStr: (str, bytes)](x: AnyStr, y: AnyStr) -> AnyStr: ... @overload def concat2(x: str, y: str) -> str: ... @@ -113,7 +109,7 @@ constraints for generic class type parameters. For example, the type parameter of the generic class ``typing.IO`` is constrained (only ``IO[str]``, ``IO[bytes]`` and ``IO[Any]`` are valid):: - class IO(Generic[AnyStr]): ... + class IO[AnyStr: (str, bytes)]: ... Invalid overload definitions diff --git a/docs/spec/protocol.rst b/docs/spec/protocol.rst index 39e7a82a6..6425d341d 100644 --- a/docs/spec/protocol.rst +++ b/docs/spec/protocol.rst @@ -266,25 +266,25 @@ Generic protocols are important. For example, ``SupportsAbs``, ``Iterable`` and ``Iterator`` are generic protocols. They are defined similar to normal non-protocol generic types:: - class Iterable(Protocol[T]): + class Iterable[T](Protocol): @abstractmethod def __iter__(self) -> Iterator[T]: ... -``Protocol[T, S, ...]`` is allowed as a shorthand for -``Protocol, Generic[T, S, ...]``. It is an error to combine -``Protocol[T, S, ...]`` with ``Generic[T, S, ...]``, or with the new syntax for -generic classes in Python 3.12 and above:: +The older syntax ``Protocol[T, S, ...]`` remains available as a shorthand for +``Protocol, Generic[T, S, ...]``. It is an error to combine the shorthand with +``Generic[T, S, ...]`` or to mix it with the new ``class Iterable[T]`` form:: - class Iterable(Protocol[T], Generic[T]): # INVALID + class Iterable[T](Protocol, Generic[T]): # INVALID ... class Iterable[T](Protocol[T]): # INVALID ... -User-defined generic protocols support explicitly declared variance. -Type checkers will warn if the inferred variance is different from -the declared variance. Examples:: +When using the generics syntax introduced in Python 3.12, the variance of +type variables is inferred. When using the pre-3.12 generics syntax, variance +must be specified. Type checkers will warn if the declared variance does not +match the protocol definition. Examples:: T = TypeVar('T') T_co = TypeVar('T_co', covariant=True) @@ -362,17 +362,15 @@ Self-types in protocols The self-types in protocols follow the :ref:`rules for other methods `. For example:: - C = TypeVar('C', bound='Copyable') class Copyable(Protocol): - def copy(self: C) -> C: + def copy[C: Copyable](self: C) -> C: class One: def copy(self) -> 'One': ... - T = TypeVar('T', bound='Other') class Other: - def copy(self: T) -> T: + def copy[T: Other](self: T) -> T: ... c: Copyable @@ -407,8 +405,7 @@ corresponding protocols are *not imported*:: # file lib.py from collections.abc import Sized - T = TypeVar('T', contravariant=True) - class ListLike(Sized, Protocol[T]): + class ListLike[T](Sized, Protocol): def append(self, x: T) -> None: pass @@ -535,13 +532,12 @@ illusion that a distinct type is provided:: In contrast, type aliases are fully supported, including generic type aliases:: - from typing import TypeVar from collections.abc import Reversible, Iterable, Sized - T = TypeVar('T') - class SizedIterable(Iterable[T], Sized, Protocol): + class SizedIterable[T](Iterable[T], Sized, Protocol): pass - CompatReversible = Reversible[T] | SizedIterable[T] + + type CompatReversible[T] = Reversible[T] | SizedIterable[T] Modules as implementations of protocols diff --git a/docs/spec/qualifiers.rst b/docs/spec/qualifiers.rst index d6105c7b2..6814692e0 100644 --- a/docs/spec/qualifiers.rst +++ b/docs/spec/qualifiers.rst @@ -292,11 +292,8 @@ Here are the specific details of the syntax: * ``Annotated`` can be used in definition of nested and generic aliases, but only if it wraps a :term:`type expression`:: - T = TypeVar("T") - Vec = Annotated[list[tuple[T, T]], MaxLen(10)] - V = Vec[int] - - V == Annotated[list[tuple[int, int]], MaxLen(10)] + type Vec[T] = Annotated[list[tuple[T, T]], MaxLen(10)] + type V = Vec[int] # Annotated[list[tuple[int, int]], MaxLen(10)] * As with most :term:`special forms `, ``Annotated`` is not assignable to ``type`` or ``type[T]``:: diff --git a/docs/spec/special-types.rst b/docs/spec/special-types.rst index ce6f336bc..dbec130c4 100644 --- a/docs/spec/special-types.rst +++ b/docs/spec/special-types.rst @@ -152,8 +152,7 @@ would be:: However using ``type[]`` and a type variable with an upper bound we can do much better:: - U = TypeVar('U', bound=User) - def new_user(user_class: type[U]) -> U: + def new_user[U: User](user_class: type[U]) -> U: ... Now when we call ``new_user()`` with a specific subclass of ``User`` a From 14426e23bab223a38f2dc6c2e0c8fb9652e0605d Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 28 Sep 2025 15:55:21 -0700 Subject: [PATCH 2/7] Update docs/spec/generics.rst Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- docs/spec/generics.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst index caca2392e..c70605110 100644 --- a/docs/spec/generics.rst +++ b/docs/spec/generics.rst @@ -338,7 +338,7 @@ Instantiating generic classes and type erasure ---------------------------------------------- User-defined generic classes can be instantiated. Suppose we write -a ``Node`` class using the new generic class syntax:: +a ``Node`` class:: class Node[T]: ... From 9dc80bca3d61b0585236b2fd9d968b1633186d89 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 28 Sep 2025 15:55:29 -0700 Subject: [PATCH 3/7] Update docs/spec/annotations.rst Co-authored-by: Alex Waygood --- docs/spec/annotations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst index 8cc79f4ab..2d91284f1 100644 --- a/docs/spec/annotations.rst +++ b/docs/spec/annotations.rst @@ -367,7 +367,7 @@ with a type variable. In this case the return type may use the same type variable, thus making that method a generic function. For example:: class Copyable: - def copy[T: 'Copyable'](self: T) -> T: + def copy[T: Copyable](self: T) -> T: # return a copy of self class C(Copyable): ... From fb21e7a83e74305b79b4d73549aa1aebb8260f9a Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 28 Sep 2025 15:55:36 -0700 Subject: [PATCH 4/7] Update docs/spec/annotations.rst Co-authored-by: Alex Waygood --- docs/spec/annotations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst index 2d91284f1..efcdb7871 100644 --- a/docs/spec/annotations.rst +++ b/docs/spec/annotations.rst @@ -379,7 +379,7 @@ of the first argument:: class C: @classmethod - def factory[T: 'C'](cls: type[T]) -> T: + def factory[T: C](cls: type[T]) -> T: # make a new instance of cls class D(C): ... From 978b20d15bd3b9db432d925267147fa747260d9a Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 28 Sep 2025 16:09:22 -0700 Subject: [PATCH 5/7] Incorporated code review feedback --- docs/spec/dataclasses.rst | 8 ++++++-- docs/spec/generics.rst | 27 +++++++++++++++++---------- docs/spec/overload.rst | 17 +++++++++-------- docs/spec/protocol.rst | 9 +++++---- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/docs/spec/dataclasses.rst b/docs/spec/dataclasses.rst index e2ad0e655..77a775745 100644 --- a/docs/spec/dataclasses.rst +++ b/docs/spec/dataclasses.rst @@ -146,7 +146,11 @@ customization of default behaviors: .. code-block:: python - def dataclass_transform[T]( + class _IdentityCallable(Protocol): + def __call__[T](self, arg: T, /) -> T: + ... + + def dataclass_transform( *, eq_default: bool = True, order_default: bool = False, @@ -154,7 +158,7 @@ customization of default behaviors: frozen_default: bool = False, field_specifiers: tuple[type | Callable[..., Any], ...] = (), **kwargs: Any, - ) -> Callable[[T], T]: ... + ) -> _IdentityCallable: ... * ``eq_default`` indicates whether the ``eq`` parameter is assumed to be True or False if it is omitted by the caller. If not specified, diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst index c70605110..fa6277e1c 100644 --- a/docs/spec/generics.rst +++ b/docs/spec/generics.rst @@ -210,15 +210,22 @@ defined class is generic if you subclass one or more other generic classes and specify type variables for their parameters. See :ref:`generic-base-classes` for details. -You can use multiple inheritance with generic classes:: +You can use multiple inheritance with ``Generic``:: - from collections.abc import Container, Iterable, Sized + from typing import TypeVar, Generic + from collections.abc import Sized, Iterable, Container + + T = TypeVar('T') - class LinkedList[T](Sized): + class LinkedList(Sized, Generic[T]): ... - class MyMapping[K, V](Iterable[tuple[K, V]], - Container[tuple[K, V]]): + K = TypeVar('K') + V = TypeVar('V') + + class MyMapping(Iterable[tuple[K, V]], + Container[tuple[K, V]], + Generic[K, V]): ... Subclassing a generic class without specifying type parameters assumes @@ -240,7 +247,7 @@ Scoping rules for type variables When using the generic class syntax introduced in Python 3.12, the location of its declaration defines its scope. When using the older syntax, the scoping -rules are more subtle and complex. +rules are more subtle and complex: * A type variable used in a generic function could be inferred to represent different types in the same code block. Example:: @@ -662,7 +669,7 @@ inline by prefixing its name with ``**`` inside a generic parameter list class CallbackWrapper[T, **P]: callback: Callable[P, T] -Prior to 3.12, the ``ParamSpec`` constructor can be used:: +Prior to 3.12, the ``ParamSpec`` constructor can be used. .. code-block:: @@ -699,8 +706,8 @@ parameter specification variable (``Callable[Concatenate[int, P], int]``\ ). "]" where ``parameter_specification_variable`` is introduced either inline (using -``**``) or via ``typing.ParamSpec`` as shown above, and ``concatenate`` is -``typing.Concatenate``. +``**`` in a type parameter list) or via ``typing.ParamSpec`` as shown above, +and ``concatenate`` is ``typing.Concatenate``. As before, ``parameters_expression``\ s by themselves are not acceptable in places where a type is expected @@ -1086,7 +1093,7 @@ type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type s ``tuple[int, str]``. In Python 3.12 and newer, type variable tuples can be introduced inline by prefixing -their name with ``*`` inside a generic parameter list. +their name with ``*`` inside a type parameter list. :: diff --git a/docs/spec/overload.rst b/docs/spec/overload.rst index b1198871b..7d9ef1bc1 100644 --- a/docs/spec/overload.rst +++ b/docs/spec/overload.rst @@ -48,7 +48,8 @@ arguments depending on the type of the callable:: @overload def map[T1, T2, S]( func: Callable[[T1, T2], S], - iter1: Iterable[T1], iter2: Iterable[T2], + iter1: Iterable[T1], + iter2: Iterable[T2], ) -> Iterator[S]: ... # ... and we could add more items to support more than two iterables @@ -91,7 +92,7 @@ A constrained ``TypeVar`` type can sometimes be used instead of using the ``@overload`` decorator. For example, the definitions of ``concat1`` and ``concat2`` in this stub file are equivalent:: - def concat1[AnyStr: (str, bytes)](x: AnyStr, y: AnyStr) -> AnyStr: ... + def concat1[S: (str, bytes)](x: S, y: S) -> S: ... @overload def concat2(x: str, y: str) -> str: ... @@ -103,13 +104,13 @@ be represented precisely using type variables. We recommend that ``@overload`` is only used in cases where a type variable is not sufficient. -Another important difference between type variables such as ``AnyStr`` -and using ``@overload`` is that the prior can also be used to define -constraints for generic class type parameters. For example, the type -parameter of the generic class ``typing.IO`` is constrained (only -``IO[str]``, ``IO[bytes]`` and ``IO[Any]`` are valid):: +Another important difference between type variables and an ``@overload`` +is that the former can also be used to define constraints for generic +class type parameters. For example, the type parameter of the generic +class ``typing.IO`` is constrained (only ``IO[str]``, ``IO[bytes]`` +and ``IO[Any]`` are valid):: - class IO[AnyStr: (str, bytes)]: ... + class IO[S: (str, bytes)]: ... Invalid overload definitions diff --git a/docs/spec/protocol.rst b/docs/spec/protocol.rst index 6425d341d..e244934f9 100644 --- a/docs/spec/protocol.rst +++ b/docs/spec/protocol.rst @@ -271,11 +271,12 @@ non-protocol generic types:: def __iter__(self) -> Iterator[T]: ... -The older syntax ``Protocol[T, S, ...]`` remains available as a shorthand for -``Protocol, Generic[T, S, ...]``. It is an error to combine the shorthand with -``Generic[T, S, ...]`` or to mix it with the new ``class Iterable[T]`` form:: +The older (pre-3.12) syntax ``Protocol[T, S, ...]`` remains available as a +shorthand for ``Protocol, Generic[T, S, ...]``. It is an error to combine the +shorthand with ``Generic[T, S, ...]`` or to mix it with the new +``class Iterable[T]`` form:: - class Iterable[T](Protocol, Generic[T]): # INVALID + class Iterable(Protocol[T], Generic[T]): # INVALID ... class Iterable[T](Protocol[T]): # INVALID From 81391f4a91f354929553499645a36df5179bc8bc Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 28 Sep 2025 18:33:31 -0700 Subject: [PATCH 6/7] Incorporated Jelle's review feedback --- docs/spec/generics.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst index fa6277e1c..ff1396a26 100644 --- a/docs/spec/generics.rst +++ b/docs/spec/generics.rst @@ -659,7 +659,7 @@ Declaration """""""""""" In Python 3.12 and newer, a parameter specification variable can be introduced -inline by prefixing its name with ``**`` inside a generic parameter list +inline by prefixing its name with ``**`` inside a type parameter list (for a function, class, or type alias). .. code-block:: @@ -730,7 +730,7 @@ specification. .. code-block:: from collections.abc import Callable - from typing import Concatenate, ParamSpec + from typing import Concatenate class X[T, **P]: f: Callable[P, int] @@ -1886,9 +1886,9 @@ literal "``...``" or another in-scope ``ParamSpec`` (see `Scoping Rules`_). class Foo[**P = [str, int]]: ... - reveal_type(Foo) # type is type[Foo[P = [str, int]]] - reveal_type(Foo()) # type is Foo[[str, int]] - reveal_type(Foo[[bool, bool]]()) # type is Foo[[bool, bool]] + reveal_type(Foo) # type is type[Foo[str, int]] + reveal_type(Foo()) # type is Foo[str, int] + reveal_type(Foo[[bool, bool]]()) # type is Foo[bool, bool] ``TypeVarTuple`` Defaults ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1901,7 +1901,7 @@ types or an unpacked, in-scope ``TypeVarTuple`` (see `Scoping Rules`_). class Foo[*Ts = *tuple[str, int]]: ... - reveal_type(Foo) # type is type[Foo[Ts = *tuple[str, int]]] + reveal_type(Foo) # type is type[Foo[str, int]] reveal_type(Foo()) # type is Foo[str, int] reveal_type(Foo[int, bool]()) # type is Foo[int, bool] From c7df3c390d94c6ca06079e8f34bf33007180f29c Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 28 Sep 2025 22:36:49 -0700 Subject: [PATCH 7/7] Addressed code review feedback from Jelle --- docs/spec/protocol.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/protocol.rst b/docs/spec/protocol.rst index e244934f9..2803da0a6 100644 --- a/docs/spec/protocol.rst +++ b/docs/spec/protocol.rst @@ -284,8 +284,8 @@ shorthand with ``Generic[T, S, ...]`` or to mix it with the new When using the generics syntax introduced in Python 3.12, the variance of type variables is inferred. When using the pre-3.12 generics syntax, variance -must be specified. Type checkers will warn if the declared variance does not -match the protocol definition. Examples:: +must be specified (unless ``infer_variance=True`` is used). Type checkers will +warn if the declared variance does not match the protocol definition. Examples:: T = TypeVar('T') T_co = TypeVar('T_co', covariant=True)