[3.11] gh-103921: Improve typing documentation (GH-104642) (#105007)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Jelle Zijlstra 2023-05-27 16:30:41 -07:00 committed by GitHub
parent dcfa8165ad
commit d34e58a1d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 190 additions and 158 deletions

View file

@ -136,6 +136,13 @@ Type aliases are useful for simplifying complex type signatures. For example::
Note that ``None`` as a type hint is a special case and is replaced by Note that ``None`` as a type hint is a special case and is replaced by
``type(None)``. ``type(None)``.
Type aliases may be marked with :data:`TypeAlias` to make it explicit that
the statement is a type alias declaration, not a normal variable assignment::
from typing import TypeAlias
Vector: TypeAlias = list[float]
.. _distinct: .. _distinct:
NewType NewType
@ -367,15 +374,15 @@ You can use multiple inheritance with :class:`Generic`::
class LinkedList(Sized, Generic[T]): class LinkedList(Sized, Generic[T]):
... ...
When inheriting from generic classes, some type variables could be fixed:: When inheriting from generic classes, some type parameters could be fixed::
from collections.abc import Mapping from collections.abc import Mapping
from typing import TypeVar from typing import TypeVar
T = TypeVar('T') T = TypeVar('T')
class MyDict(Mapping[str, T]): class MyDict(Mapping[str, T]):
... ...
In this case ``MyDict`` has a single parameter, ``T``. In this case ``MyDict`` has a single parameter, ``T``.
@ -387,7 +394,7 @@ not generic but implicitly inherits from ``Iterable[Any]``::
class MyIterable(Iterable): # Same as Iterable[Any] class MyIterable(Iterable): # Same as Iterable[Any]
User defined generic type aliases are also supported. Examples:: User-defined generic type aliases are also supported. Examples::
from collections.abc import Iterable from collections.abc import Iterable
from typing import TypeVar from typing import TypeVar
@ -423,7 +430,6 @@ to this is that a list of types can be used to substitute a :class:`ParamSpec`::
>>> Z[int, [dict, float]] >>> Z[int, [dict, float]]
__main__.Z[int, (<class 'dict'>, <class 'float'>)] __main__.Z[int, (<class 'dict'>, <class 'float'>)]
Furthermore, a generic with only one parameter specification variable will accept Furthermore, a generic with only one parameter specification variable will accept
parameter lists in the forms ``X[[Type1, Type2, ...]]`` and also parameter lists in the forms ``X[[Type1, Type2, ...]]`` and also
``X[Type1, Type2, ...]`` for aesthetic reasons. Internally, the latter is converted ``X[Type1, Type2, ...]`` for aesthetic reasons. Internally, the latter is converted
@ -665,20 +671,20 @@ These can be used as types in annotations and do not support ``[]``.
This can be used to define a function that should never be This can be used to define a function that should never be
called, or a function that never returns:: called, or a function that never returns::
from typing import Never from typing import Never
def never_call_me(arg: Never) -> None: def never_call_me(arg: Never) -> None:
pass pass
def int_or_str(arg: int | str) -> None: def int_or_str(arg: int | str) -> None:
never_call_me(arg) # type checker error never_call_me(arg) # type checker error
match arg: match arg:
case int(): case int():
print("It's an int") print("It's an int")
case str(): case str():
print("It's a str") print("It's a str")
case _: case _:
never_call_me(arg) # ok, arg is of type Never never_call_me(arg) # ok, arg is of type Never
.. versionadded:: 3.11 .. versionadded:: 3.11
@ -712,9 +718,9 @@ These can be used as types in annotations and do not support ``[]``.
from typing import Self from typing import Self
class Foo: class Foo:
def return_self(self) -> Self: def return_self(self) -> Self:
... ...
return self return self
This annotation is semantically equivalent to the following, This annotation is semantically equivalent to the following,
@ -725,16 +731,16 @@ These can be used as types in annotations and do not support ``[]``.
Self = TypeVar("Self", bound="Foo") Self = TypeVar("Self", bound="Foo")
class Foo: class Foo:
def return_self(self: Self) -> Self: def return_self(self: Self) -> Self:
... ...
return self return self
In general if something currently follows the pattern of:: In general if something currently follows the pattern of::
class Foo: class Foo:
def return_self(self) -> "Foo": def return_self(self) -> "Foo":
... ...
return self return self
You should use :data:`Self` as calls to ``SubclassOfFoo.return_self`` would have You should use :data:`Self` as calls to ``SubclassOfFoo.return_self`` would have
``Foo`` as the return type and not ``SubclassOfFoo``. ``Foo`` as the return type and not ``SubclassOfFoo``.
@ -1249,7 +1255,8 @@ These can be used as types in annotations using ``[]``, each having a unique syn
Building generic types Building generic types
"""""""""""""""""""""" """"""""""""""""""""""
These are not used in annotations. They are building blocks for creating generic types. The following objects are not used directly in annotations. Instead, they are building blocks
for creating generic types.
.. class:: Generic .. class:: Generic
@ -1275,177 +1282,204 @@ These are not used in annotations. They are building blocks for creating generic
except KeyError: except KeyError:
return default return default
.. class:: TypeVar .. class:: TypeVar(name, *constraints, bound=None, covariant=False, contravariant=False)
Type variable. Type variable.
Usage:: Usage::
T = TypeVar('T') # Can be anything T = TypeVar('T') # Can be anything
S = TypeVar('S', bound=str) # Can be any subtype of str S = TypeVar('S', bound=str) # Can be any subtype of str
A = TypeVar('A', str, bytes) # Must be exactly str or bytes A = TypeVar('A', str, bytes) # Must be exactly str or bytes
Type variables exist primarily for the benefit of static type Type variables exist primarily for the benefit of static type
checkers. They serve as the parameters for generic types as well checkers. They serve as the parameters for generic types as well
as for generic function definitions. See :class:`Generic` for more as for generic function and type alias definitions.
information on generic types. Generic functions work as follows:: See :class:`Generic` for more
information on generic types. Generic functions work as follows::
def repeat(x: T, n: int) -> Sequence[T]: def repeat(x: T, n: int) -> Sequence[T]:
"""Return a list containing n references to x.""" """Return a list containing n references to x."""
return [x]*n return [x]*n
def print_capitalized(x: S) -> S: def print_capitalized(x: S) -> S:
"""Print x capitalized, and return x.""" """Print x capitalized, and return x."""
print(x.capitalize()) print(x.capitalize())
return x return x
def concatenate(x: A, y: A) -> A: def concatenate(x: A, y: A) -> A:
"""Add two strings or bytes objects together.""" """Add two strings or bytes objects together."""
return x + y return x + y
Note that type variables can be *bound*, *constrained*, or neither, but Note that type variables can be *bound*, *constrained*, or neither, but
cannot be both bound *and* constrained. cannot be both bound *and* constrained.
Bound type variables and constrained type variables have different Created type variables may be explicitly marked covariant or contravariant by passing
semantics in several important ways. Using a *bound* type variable means ``covariant=True`` or ``contravariant=True``.
that the ``TypeVar`` will be solved using the most specific type possible:: By default, type variables are invariant.
See :pep:`484` and :pep:`695` for more details.
x = print_capitalized('a string') Bound type variables and constrained type variables have different
reveal_type(x) # revealed type is str semantics in several important ways. Using a *bound* type variable means
that the ``TypeVar`` will be solved using the most specific type possible::
class StringSubclass(str): x = print_capitalized('a string')
pass reveal_type(x) # revealed type is str
y = print_capitalized(StringSubclass('another string')) class StringSubclass(str):
reveal_type(y) # revealed type is StringSubclass pass
z = print_capitalized(45) # error: int is not a subtype of str y = print_capitalized(StringSubclass('another string'))
reveal_type(y) # revealed type is StringSubclass
Type variables can be bound to concrete types, abstract types (ABCs or z = print_capitalized(45) # error: int is not a subtype of str
protocols), and even unions of types::
U = TypeVar('U', bound=str|bytes) # Can be any subtype of the union str|bytes Type variables can be bound to concrete types, abstract types (ABCs or
V = TypeVar('V', bound=SupportsAbs) # Can be anything with an __abs__ method protocols), and even unions of types::
.. _typing-constrained-typevar: U = TypeVar('U', bound=str|bytes) # Can be any subtype of the union str|bytes
V = TypeVar('V', bound=SupportsAbs) # Can be anything with an __abs__ method
Using a *constrained* type variable, however, means that the ``TypeVar`` .. _typing-constrained-typevar:
can only ever be solved as being exactly one of the constraints given::
a = concatenate('one', 'two') Using a *constrained* type variable, however, means that the ``TypeVar``
reveal_type(a) # revealed type is str can only ever be solved as being exactly one of the constraints given::
b = concatenate(StringSubclass('one'), StringSubclass('two')) a = concatenate('one', 'two')
reveal_type(b) # revealed type is str, despite StringSubclass being passed in reveal_type(a) # revealed type is str
c = concatenate('one', b'two') # error: type variable 'A' can be either str or bytes in a function call, but not both b = concatenate(StringSubclass('one'), StringSubclass('two'))
reveal_type(b) # revealed type is str, despite StringSubclass being passed in
At runtime, ``isinstance(x, T)`` will raise :exc:`TypeError`. In general, c = concatenate('one', b'two') # error: type variable 'A' can be either str or bytes in a function call, but not both
:func:`isinstance` and :func:`issubclass` should not be used with types.
Type variables may be marked covariant or contravariant by passing At runtime, ``isinstance(x, T)`` will raise :exc:`TypeError`.
``covariant=True`` or ``contravariant=True``. See :pep:`484` for more
details. By default, type variables are invariant.
.. class:: TypeVarTuple .. attribute:: __name__
Type variable tuple. A specialized form of :class:`type variable <TypeVar>` The name of the type variable.
that enables *variadic* generics.
A normal type variable enables parameterization with a single type. A type .. attribute:: __covariant__
variable tuple, in contrast, allows parameterization with an
*arbitrary* number of types by acting like an *arbitrary* number of type
variables wrapped in a tuple. For example::
T = TypeVar('T') Whether the type var has been marked as covariant.
Ts = TypeVarTuple('Ts')
def move_first_element_to_last(tup: tuple[T, *Ts]) -> tuple[*Ts, T]: .. attribute:: __contravariant__
return (*tup[1:], tup[0])
# T is bound to int, Ts is bound to () Whether the type var has been marked as contravariant.
# Return value is (1,), which has type tuple[int]
move_first_element_to_last(tup=(1,))
# T is bound to int, Ts is bound to (str,) .. attribute:: __bound__
# Return value is ('spam', 1), which has type tuple[str, int]
move_first_element_to_last(tup=(1, 'spam'))
# T is bound to int, Ts is bound to (str, float) The bound of the type variable, if any.
# Return value is ('spam', 3.0, 1), which has type tuple[str, float, int]
move_first_element_to_last(tup=(1, 'spam', 3.0))
# This fails to type check (and fails at runtime) .. attribute:: __constraints__
# because tuple[()] is not compatible with tuple[T, *Ts]
# (at least one element is required)
move_first_element_to_last(tup=())
Note the use of the unpacking operator ``*`` in ``tuple[T, *Ts]``. A tuple containing the constraints of the type variable, if any.
Conceptually, you can think of ``Ts`` as a tuple of type variables
``(T1, T2, ...)``. ``tuple[T, *Ts]`` would then become
``tuple[T, *(T1, T2, ...)]``, which is equivalent to
``tuple[T, T1, T2, ...]``. (Note that in older versions of Python, you might
see this written using :data:`Unpack <Unpack>` instead, as
``Unpack[Ts]``.)
Type variable tuples must *always* be unpacked. This helps distinguish type .. class:: TypeVarTuple(name)
variable tuples from normal type variables::
x: Ts # Not valid Type variable tuple. A specialized form of :class:`type variable <TypeVar>`
x: tuple[Ts] # Not valid that enables *variadic* generics.
x: tuple[*Ts] # The correct way to do it
Type variable tuples can be used in the same contexts as normal type Usage::
variables. For example, in class definitions, arguments, and return types::
Shape = TypeVarTuple('Shape') T = TypeVar("T")
class Array(Generic[*Shape]): Ts = TypeVarTuple("Ts")
def __getitem__(self, key: tuple[*Shape]) -> float: ...
def __abs__(self) -> "Array[*Shape]": ...
def get_shape(self) -> tuple[*Shape]: ...
Type variable tuples can be happily combined with normal type variables:: def move_first_element_to_last(tup: tuple[T, *Ts]) -> tuple[*Ts, T]:
return (*tup[1:], tup[0])
DType = TypeVar('DType') A normal type variable enables parameterization with a single type. A type
variable tuple, in contrast, allows parameterization with an
*arbitrary* number of types by acting like an *arbitrary* number of type
variables wrapped in a tuple. For example::
class Array(Generic[DType, *Shape]): # This is fine # T is bound to int, Ts is bound to ()
pass # Return value is (1,), which has type tuple[int]
move_first_element_to_last(tup=(1,))
class Array2(Generic[*Shape, DType]): # This would also be fine # T is bound to int, Ts is bound to (str,)
pass # Return value is ('spam', 1), which has type tuple[str, int]
move_first_element_to_last(tup=(1, 'spam'))
float_array_1d: Array[float, Height] = Array() # Totally fine # T is bound to int, Ts is bound to (str, float)
int_array_2d: Array[int, Height, Width] = Array() # Yup, fine too # Return value is ('spam', 3.0, 1), which has type tuple[str, float, int]
move_first_element_to_last(tup=(1, 'spam', 3.0))
However, note that at most one type variable tuple may appear in a single # This fails to type check (and fails at runtime)
list of type arguments or type parameters:: # because tuple[()] is not compatible with tuple[T, *Ts]
# (at least one element is required)
move_first_element_to_last(tup=())
x: tuple[*Ts, *Ts] # Not valid Note the use of the unpacking operator ``*`` in ``tuple[T, *Ts]``.
class Array(Generic[*Shape, *Shape]): # Not valid Conceptually, you can think of ``Ts`` as a tuple of type variables
pass ``(T1, T2, ...)``. ``tuple[T, *Ts]`` would then become
``tuple[T, *(T1, T2, ...)]``, which is equivalent to
``tuple[T, T1, T2, ...]``. (Note that in older versions of Python, you might
see this written using :data:`Unpack <Unpack>` instead, as
``Unpack[Ts]``.)
Finally, an unpacked type variable tuple can be used as the type annotation Type variable tuples must *always* be unpacked. This helps distinguish type
of ``*args``:: variable tuples from normal type variables::
def call_soon( x: Ts # Not valid
callback: Callable[[*Ts], None], x: tuple[Ts] # Not valid
*args: *Ts x: tuple[*Ts] # The correct way to do it
) -> None:
...
callback(*args)
In contrast to non-unpacked annotations of ``*args`` - e.g. ``*args: int``, Type variable tuples can be used in the same contexts as normal type
which would specify that *all* arguments are ``int`` - ``*args: *Ts`` variables. For example, in class definitions, arguments, and return types::
enables reference to the types of the *individual* arguments in ``*args``.
Here, this allows us to ensure the types of the ``*args`` passed
to ``call_soon`` match the types of the (positional) arguments of
``callback``.
See :pep:`646` for more details on type variable tuples. Shape = TypeVarTuple("Shape")
class Array(Generic[*Shape]):
def __getitem__(self, key: tuple[*Shape]) -> float: ...
def __abs__(self) -> "Array[*Shape]": ...
def get_shape(self) -> tuple[*Shape]: ...
.. versionadded:: 3.11 Type variable tuples can be happily combined with normal type variables::
DType = TypeVar('DType')
class Array(Generic[DType, *Shape]): # This is fine
pass
class Array2(Generic[*Shape, DType]): # This would also be fine
pass
float_array_1d: Array[float, Height] = Array() # Totally fine
int_array_2d: Array[int, Height, Width] = Array() # Yup, fine too
However, note that at most one type variable tuple may appear in a single
list of type arguments or type parameters::
x: tuple[*Ts, *Ts] # Not valid
class Array(Generic[*Shape, *Shape]): # Not valid
pass
Finally, an unpacked type variable tuple can be used as the type annotation
of ``*args``::
def call_soon(
callback: Callable[[*Ts], None],
*args: *Ts
) -> None:
...
callback(*args)
In contrast to non-unpacked annotations of ``*args`` - e.g. ``*args: int``,
which would specify that *all* arguments are ``int`` - ``*args: *Ts``
enables reference to the types of the *individual* arguments in ``*args``.
Here, this allows us to ensure the types of the ``*args`` passed
to ``call_soon`` match the types of the (positional) arguments of
``callback``.
See :pep:`646` for more details on type variable tuples.
.. attribute:: __name__
The name of the type variable tuple.
.. versionadded:: 3.11
.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False) .. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False)
@ -1510,6 +1544,10 @@ These are not used in annotations. They are building blocks for creating generic
``P.args`` and ``P.kwargs`` are instances respectively of ``P.args`` and ``P.kwargs`` are instances respectively of
:class:`ParamSpecArgs` and :class:`ParamSpecKwargs`. :class:`ParamSpecArgs` and :class:`ParamSpecKwargs`.
.. attribute:: __name__
The name of the parameter specification.
Parameter specification variables created with ``covariant=True`` or Parameter specification variables created with ``covariant=True`` or
``contravariant=True`` can be used to declare covariant or contravariant ``contravariant=True`` can be used to declare covariant or contravariant
generic types. The ``bound`` argument is also accepted, similar to generic types. The ``bound`` argument is also accepted, similar to
@ -1672,6 +1710,8 @@ These are not used in annotations. They are building blocks for declaring types.
Protocol classes can be generic, for example:: Protocol classes can be generic, for example::
T = TypeVar("T")
class GenProto(Protocol[T]): class GenProto(Protocol[T]):
def meth(self) -> T: def meth(self) -> T:
... ...
@ -2152,8 +2192,8 @@ Corresponding to collections in :mod:`collections.abc`
A generic version of :class:`collections.abc.Mapping`. A generic version of :class:`collections.abc.Mapping`.
This type can be used as follows:: This type can be used as follows::
def get_position_in_index(word_list: Mapping[str, int], word: str) -> int: def get_position_in_index(word_list: Mapping[str, int], word: str) -> int:
return word_list[word] return word_list[word]
.. deprecated:: 3.9 .. deprecated:: 3.9
:class:`collections.abc.Mapping` now supports subscripting (``[]``). :class:`collections.abc.Mapping` now supports subscripting (``[]``).

View file

@ -1177,17 +1177,9 @@ class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
'''Add two numbers together.''' '''Add two numbers together.'''
return x + y return x + y
Parameter specification variables defined with covariant=True or
contravariant=True can be used to declare covariant or contravariant
generic types. These keyword arguments are valid, but their actual semantics
are yet to be decided. See PEP 612 for details.
Parameter specification variables can be introspected. e.g.: Parameter specification variables can be introspected. e.g.:
P.__name__ == 'P' P.__name__ == 'P'
P.__bound__ == None
P.__covariant__ == False
P.__contravariant__ == False
Note that only parameter specification variables defined in global scope can Note that only parameter specification variables defined in global scope can
be pickled. be pickled.