Skip to content

Commit d5ed8a8

Browse files
authored
[3.10] bpo-46769: Improve documentation for typing.TypeVar (GH-31712) (GH-31941)
* [3.10] bpo-46769: Improve documentation for `typing.TypeVar` (GH-31712) Co-authored-by: Jelle Zijlstra <[email protected]> (cherry picked from commit 81b425d) * Remove references to `reveal_type`, add new section on `self` types
1 parent f163ad2 commit d5ed8a8

File tree

2 files changed

+89
-21
lines changed

2 files changed

+89
-21
lines changed

Doc/library/typing.rst

+88-21
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ subscription to denote expected types for container elements.
247247
def notify_by_email(employees: Sequence[Employee],
248248
overrides: Mapping[str, str]) -> None: ...
249249

250-
Generics can be parameterized by using a new factory available in typing
250+
Generics can be parameterized by using a factory available in typing
251251
called :class:`TypeVar`.
252252

253253
::
@@ -304,16 +304,16 @@ that ``LoggedVar[t]`` is valid as a type::
304304
for var in vars:
305305
var.set(0)
306306

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

310-
from typing import TypeVar, Generic
311-
...
310+
from typing import TypeVar, Generic, Sequence
312311

313-
T = TypeVar('T')
312+
T = TypeVar('T', contravariant=True)
313+
B = TypeVar('B', bound=Sequence[bytes], covariant=True)
314314
S = TypeVar('S', int, str)
315315

316-
class StrangePair(Generic[T, S]):
316+
class WeirdTrio(Generic[T, B, S]):
317317
...
318318

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

10861086
T = TypeVar('T') # Can be anything
1087-
A = TypeVar('A', str, bytes) # Must be str or bytes
1087+
S = TypeVar('S', bound=str) # Can be any subtype of str
1088+
A = TypeVar('A', str, bytes) # Must be exactly str or bytes
10881089

10891090
Type variables exist primarily for the benefit of static type
10901091
checkers. They serve as the parameters for generic types as well
@@ -1095,25 +1096,91 @@ These are not used in annotations. They are building blocks for creating generic
10951096
"""Return a list containing n references to x."""
10961097
return [x]*n
10971098

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

1102-
The latter example's signature is essentially the overloading
1103-
of ``(str, str) -> str`` and ``(bytes, bytes) -> bytes``. Also note
1104-
that if the arguments are instances of some subclass of :class:`str`,
1105-
the return type is still plain :class:`str`.
1100+
def print_capitalized(x: S) -> S:
1101+
"""Print x capitalized, and return x."""
1102+
print(x.capitalize())
1103+
return x
1104+
1105+
1106+
def concatenate(x: A, y: A) -> A:
1107+
"""Add two strings or bytes objects together."""
1108+
return x + y
1109+
1110+
Note that type variables can be *bound*, *constrained*, or neither, but
1111+
cannot be both bound *and* constrained.
1112+
1113+
Constrained type variables and bound type variables have different
1114+
semantics in several important ways. Using a *constrained* type variable
1115+
means that the ``TypeVar`` can only ever be solved as being exactly one of
1116+
the constraints given::
1117+
1118+
a = concatenate('one', 'two') # Ok, variable 'a' has type 'str'
1119+
b = concatenate(StringSubclass('one'), StringSubclass('two')) # Inferred type of variable 'b' is 'str',
1120+
# despite 'StringSubclass' being passed in
1121+
c = concatenate('one', b'two') # error: type variable 'A' can be either 'str' or 'bytes' in a function call, but not both
1122+
1123+
Using a *bound* type variable, however, means that the ``TypeVar`` will be
1124+
solved using the most specific type possible::
1125+
1126+
print_capitalized('a string') # Ok, output has type 'str'
1127+
1128+
class StringSubclass(str):
1129+
pass
1130+
1131+
print_capitalized(StringSubclass('another string')) # Ok, output has type 'StringSubclass'
1132+
print_capitalized(45) # error: int is not a subtype of str
1133+
1134+
Type variables can be bound to concrete types, abstract types (ABCs or
1135+
protocols), and even unions of types::
1136+
1137+
U = TypeVar('U', bound=str|bytes) # Can be any subtype of the union str|bytes
1138+
V = TypeVar('V', bound=SupportsAbs) # Can be anything with an __abs__ method
1139+
1140+
Bound type variables are particularly useful for annotating
1141+
:func:`classmethods <classmethod>` that serve as alternative constructors.
1142+
In the following example (©
1143+
`Raymond Hettinger <https://www.youtube.com/watch?v=HTLu2DFOdTg>`_), the
1144+
type variable ``C`` is bound to the ``Circle`` class through the use of a
1145+
forward reference. Using this type variable to annotate the
1146+
``with_circumference`` classmethod, rather than hardcoding the return type
1147+
as ``Circle``, means that a type checker can correctly infer the return
1148+
type even if the method is called on a subclass::
1149+
1150+
import math
1151+
1152+
C = TypeVar('C', bound='Circle')
1153+
1154+
class Circle:
1155+
"""An abstract circle"""
1156+
1157+
def __init__(self, radius: float) -> None:
1158+
self.radius = radius
1159+
1160+
# Use a type variable to show that the return type
1161+
# will always be an instance of whatever `cls` is
1162+
@classmethod
1163+
def with_circumference(cls: type[C], circumference: float) -> C:
1164+
"""Create a circle with the specified circumference"""
1165+
radius = circumference / (math.pi * 2)
1166+
return cls(radius)
1167+
1168+
1169+
class Tire(Circle):
1170+
"""A specialised circle (made out of rubber)"""
1171+
1172+
MATERIAL = 'rubber'
1173+
1174+
1175+
c = Circle.with_circumference(3) # Ok, variable 'c' has type 'Circle'
1176+
t = Tire.with_circumference(4) # Ok, variable 't' has type 'Tire' (not 'Circle')
11061177

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

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

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

@@ -1215,7 +1282,7 @@ These are not used in annotations. They are building blocks for creating generic
12151282

12161283
.. data:: AnyStr
12171284

1218-
``AnyStr`` is a type variable defined as
1285+
``AnyStr`` is a :class:`constrained type variable <TypeVar>` defined as
12191286
``AnyStr = TypeVar('AnyStr', str, bytes)``.
12201287

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

Doc/tools/susp-ignored.csv

+1
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,4 @@ library/typing,,`,# Type of ``val`` is narrowed to ``str``
384384
library/typing,,`,"# Else, type of ``val`` is narrowed to ``float``."
385385
library/typing,,`,# Type of ``val`` is narrowed to ``List[str]``.
386386
library/typing,,`,# Type of ``val`` remains as ``List[object]``.
387+
library/typing,,`, # will always be an instance of whatever `cls` is

0 commit comments

Comments
 (0)