]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.9] bpo-46769: Improve documentation for `typing.TypeVar` (GH-31712) (GH-31941...
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Wed, 23 Mar 2022 18:19:23 +0000 (11:19 -0700)
committerGitHub <noreply@github.com>
Wed, 23 Mar 2022 18:19:23 +0000 (19:19 +0100)
* [3.9] [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 <jelle.zijlstra@gmail.com>
(cherry picked from commit 81b425d4dc43b60dd11a3e9abc5c84a4b8b384db)

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

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
* remove unused susp allowlist

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Doc/library/typing.rst
Doc/tools/susp-ignored.csv

index 13fc418e9274b46eaafbd9c5af17600893d77da3..2689c7f2938716326c55fd1542534e0910ec728f 100644 (file)
@@ -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`.
 
 ::
@@ -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.
@@ -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
@@ -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
index 48480ed567713a24a4b6c4b09e5e8c5f6a152956..67e449316e4a21d5b1b07fbf3342d716f35ea110 100644 (file)
@@ -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