Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-46769: Improve documentation for typing.TypeVar #31712

Merged
merged 3 commits into from
Mar 16, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 55 additions & 21 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ subscription to denote expected types for container elements.
def notify_by_email(employees: Sequence[Employee],
overrides: Mapping[str, str]) -> None: ...

Generics can be parameterized by using a new factory available in typing
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeVar isn't really very new anymore

Generics can be parameterized by using a factory available in typing
called :class:`TypeVar`.

::
Expand Down Expand Up @@ -302,16 +302,16 @@ that ``LoggedVar[t]`` is valid as a type::
for var in vars:
var.set(0)

A generic type can have any number of type variables, and type variables may
be constrained::
A generic type can have any number of type variables. All varieties of
:class:`TypeVar` are permissible as parameters for a generic type::

from typing import TypeVar, Generic
...
from typing import TypeVar, Generic, Sequence

T = TypeVar('T')
T = TypeVar('T', contravariant=True)
B = TypeVar('B', bound=Sequence[bytes], covariant=True)
S = TypeVar('S', int, str)

class StrangePair(Generic[T, S]):
class WeirdTrio(Generic[T, B, S]):
...

Each type variable argument to :class:`Generic` must be distinct.
Expand Down Expand Up @@ -1161,7 +1161,8 @@ These are not used in annotations. They are building blocks for creating generic
Usage::

T = TypeVar('T') # Can be anything
A = TypeVar('A', str, bytes) # Must be str or bytes
S = TypeVar('S', bound=str) # Can be any subtype of str
A = TypeVar('A', str, bytes) # Must be exactly str or bytes

Type variables exist primarily for the benefit of static type
checkers. They serve as the parameters for generic types as well
Expand All @@ -1172,25 +1173,58 @@ These are not used in annotations. They are building blocks for creating generic
"""Return a list containing n references to x."""
return [x]*n

def longest(x: A, y: A) -> A:
"""Return the longest of two strings."""
return x if len(x) >= len(y) else y

The latter example's signature is essentially the overloading
of ``(str, str) -> str`` and ``(bytes, bytes) -> bytes``. Also note
that if the arguments are instances of some subclass of :class:`str`,
the return type is still plain :class:`str`.
def print_capitalized(x: S) -> S:
"""Print x capitalized, and return x."""
print(x.capitalize())
return x


def concatenate(x: A, y: A) -> A:
"""Add two strings or bytes objects together."""
return x + y

Note that type variables can be *bound*, *constrained*, or neither, but
cannot be both bound *and* constrained.

Bound type variables and constrained type variables have different
semantics in several important ways. Using a *bound* type variable means
that the ``TypeVar`` will be solved using the most specific type possible::

x = print_capitalized('a string')
reveal_type(x) # revealed type is str

class StringSubclass(str):
pass

y = print_capitalized(StringSubclass('another string'))
reveal_type(y) # revealed type is StringSubclass

z = print_capitalized(45) # error: int is not a subtype of str

Type variables can be bound to concrete types, abstract types (ABCs or
protocols), and even unions of types::

U = TypeVar('U', bound=str|bytes) # Can be any subtype of the union str|bytes
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
V = TypeVar('V', bound=SupportsAbs) # Can be anything with an __abs__ method

Using a *constrained* type variable, however, means that the ``TypeVar``
can only ever be solved as being exactly one of the constraints given::

a = concatenate('one', 'two')
reveal_type(a) # revealed type is str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using reveal_type() here is nice, but may make it tricky to backport this docs patch, because the 3.10 and 3.9 docs say nothing about reveal_type(). Then again, we've been using reveal_type() in PEPs for years too.

Copy link
Member Author

@AlexWaygood AlexWaygood Mar 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, do the typing docs not use reveal_type at all at the moment? I might have got the typing docs and PEP 484 confused in my head slightly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No (on 3.10), and neither does PEP 484. However, several later typing PEPs used (but did not define) reveal_type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll reword this bit then, thanks for the catch!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good to use reveal_type() in the long term though. Maybe leave it as is, and we can reword in the 3.10/3.9 backport PRs later?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, sure!


b = concatenate(StringSubclass('one'), StringSubclass('two'))
reveal_type(b) # revealed type is str, despite StringSubclass being passed in

c = concatenate('one', b'two') # error: type variable 'A' can be either str or bytes in a function call, but not both

At runtime, ``isinstance(x, T)`` will raise :exc:`TypeError`. In general,
:func:`isinstance` and :func:`issubclass` should not be used with types.

Type variables may be marked covariant or contravariant by passing
``covariant=True`` or ``contravariant=True``. See :pep:`484` for more
details. By default type variables are invariant. Alternatively,
a type variable may specify an upper bound using ``bound=<type>``.
This means that an actual type substituted (explicitly or implicitly)
for the type variable must be a subclass of the boundary type,
see :pep:`484`.
details. By default, type variables are invariant.

.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False)

Expand Down Expand Up @@ -1292,7 +1326,7 @@ These are not used in annotations. They are building blocks for creating generic

.. data:: AnyStr

``AnyStr`` is a type variable defined as
``AnyStr`` is a :class:`constrained type variable <TypeVar>` defined as
``AnyStr = TypeVar('AnyStr', str, bytes)``.

It is meant to be used for functions that may accept any kind of string
Expand Down