Skip to content

Commit

Permalink
[3.9] bpo-46769: Improve documentation for typing.TypeVar (pythonGH…
Browse files Browse the repository at this point in the history
…-31712) (pythonGH-31941) (pythonGH-32067)

* [3.9] [3.10] bpo-46769: Improve documentation for `typing.TypeVar` (pythonGH-31712) (pythonGH-31941)

* [3.10] bpo-46769: Improve documentation for `typing.TypeVar` (pythonGH-31712)

Co-authored-by: Jelle Zijlstra <[email protected]>
(cherry picked from commit 81b425d)

* Remove references to `reveal_type`, add new section on `self` types.
(cherry picked from commit d5ed8a8)

Co-authored-by: Alex Waygood <[email protected]>

* remove unused susp allowlist

Co-authored-by: Alex Waygood <[email protected]>
  • Loading branch information
2 people authored and hello-adam committed Jun 2, 2022
1 parent 54fa360 commit d167260
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 21 deletions.
109 changes: 88 additions & 21 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,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
Generics can be parameterized by using a factory available in typing
called :class:`TypeVar`.

::
Expand Down Expand Up @@ -271,16 +271,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 @@ -856,7 +856,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 @@ -867,29 +868,95 @@ 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.

Constrained type variables and bound type variables have different
semantics in several important ways. Using a *constrained* type variable
means that the ``TypeVar`` can only ever be solved as being exactly one of
the constraints given::

a = concatenate('one', 'two') # Ok, variable 'a' has type 'str'
b = concatenate(StringSubclass('one'), StringSubclass('two')) # Inferred type of variable 'b' 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

Using a *bound* type variable, however, means that the ``TypeVar`` will be
solved using the most specific type possible::

print_capitalized('a string') # Ok, output has type 'str'

class StringSubclass(str):
pass

print_capitalized(StringSubclass('another string')) # Ok, output has type 'StringSubclass'
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
V = TypeVar('V', bound=SupportsAbs) # Can be anything with an __abs__ method

Bound type variables are particularly useful for annotating
:func:`classmethods <classmethod>` that serve as alternative constructors.
In the following example (©
`Raymond Hettinger <https://www.youtube.com/watch?v=HTLu2DFOdTg>`_), the
type variable ``C`` is bound to the ``Circle`` class through the use of a
forward reference. Using this type variable to annotate the
``with_circumference`` classmethod, rather than hardcoding the return type
as ``Circle``, means that a type checker can correctly infer the return
type even if the method is called on a subclass::

import math

C = TypeVar('C', bound='Circle')

class Circle:
"""An abstract circle"""

def __init__(self, radius: float) -> None:
self.radius = radius

# Use a type variable to show that the return type
# will always be an instance of whatever `cls` is
@classmethod
def with_circumference(cls: type[C], circumference: float) -> C:
"""Create a circle with the specified circumference"""
radius = circumference / (math.pi * 2)
return cls(radius)


class Tire(Circle):
"""A specialised circle (made out of rubber)"""

MATERIAL = 'rubber'


c = Circle.with_circumference(3) # Ok, variable 'c' has type 'Circle'
t = Tire.with_circumference(4) # Ok, variable 't' has type 'Tire' (not 'Circle')

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.

.. 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
1 change: 1 addition & 0 deletions Doc/tools/susp-ignored.csv
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,4 @@ whatsnew/changelog,,::,default::DeprecationWarning
library/importlib.metadata,,:main,"EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')"
library/importlib.metadata,,`,loading the metadata for packages for the indicated ``context``.
library/re,,`,"`"
library/typing,,`, # will always be an instance of whatever `cls` is

0 comments on commit d167260

Please sign in to comment.