From 11276cd9c49faea66ce7760f26a238d1edbf6421 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 2 Jan 2021 08:45:50 +0800 Subject: [PATCH] bpo-41559: Documentation for PEP 612 (GH-24000) --- Doc/library/stdtypes.rst | 5 + Doc/library/typing.rst | 204 ++++++++++++++++++++++++++++++++++++++- Doc/whatsnew/3.10.rst | 22 +++++ Lib/typing.py | 15 +-- 4 files changed, 238 insertions(+), 8 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 2869378bbda..2331849c02e 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -4959,6 +4959,11 @@ All parameterized generics implement special read-only attributes. (~T,) + .. note:: + A ``GenericAlias`` object with :class:`typing.ParamSpec` parameters may not + have correct ``__parameters__`` after substitution because + :class:`typing.ParamSpec` is intended primarily for static type checking. + .. seealso:: * :pep:`585` -- "Type Hinting Generics In Standard Collections" diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index b07bb8943d1..d74f8bcc27a 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -18,7 +18,8 @@ -------------- This module provides runtime support for type hints as specified by -:pep:`484`, :pep:`526`, :pep:`544`, :pep:`586`, :pep:`589`, :pep:`591`, and :pep:`613`. +:pep:`484`, :pep:`526`, :pep:`544`, :pep:`586`, :pep:`589`, :pep:`591`, +:pep:`612` and :pep:`613`. The most fundamental support consists of the types :data:`Any`, :data:`Union`, :data:`Tuple`, :data:`Callable`, :class:`TypeVar`, and :class:`Generic`. For full specification please see :pep:`484`. For @@ -171,6 +172,22 @@ It is possible to declare the return type of a callable without specifying the call signature by substituting a literal ellipsis for the list of arguments in the type hint: ``Callable[..., ReturnType]``. +Callables which take other callables as arguments may indicate that their +parameter types are dependent on each other using :class:`ParamSpec`. +Additionally, if that callable adds or removes arguments from other +callables, the :data:`Concatenate` operator may be used. They +take the form ``Callable[ParamSpecVariable, ReturnType]`` and +``Callable[Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable], ReturnType]`` +respectively. + +.. versionchanged:: 3.10 + ``Callable`` now supports :class:`ParamSpec` and :data:`Concatenate`. + See :pep:`612` for more information. + +.. seealso:: + The documentation for :class:`ParamSpec` and :class:`Concatenate` provide + examples of usage in ``Callable``. + .. _generics: Generics @@ -316,6 +333,43 @@ User defined generic type aliases are also supported. Examples:: .. versionchanged:: 3.7 :class:`Generic` no longer has a custom metaclass. +User-defined generics for parameter expressions are also supported via parameter +specification variables in the form ``Generic[P]``. The behavior is consistent +with type variables' described above as parameter specification variables are +treated by the typing module as a specialized type variable. The one exception +to this is that a list of types can be used to substitute a :class:`ParamSpec`:: + + >>> from typing import Generic, ParamSpec, TypeVar + + >>> T = TypeVar('T') + >>> P = ParamSpec('P') + + >>> class Z(Generic[T, P]): ... + ... + >>> Z[int, [dict, float]] + __main__.Z[int, (, )] + + +Furthermore, a generic with only one parameter specification variable will accept +parameter lists in the forms ``X[[Type1, Type2, ...]]`` and also +``X[Type1, Type2, ...]`` for aesthetic reasons. Internally, the latter is converted +to the former and are thus equivalent:: + + >>> class X(Generic[P]): ... + ... + >>> X[int, str] + __main__.X[(, )] + >>> X[[int, str]] + __main__.X[(, )] + +Do note that generics with :class:`ParamSpec` may not have correct +``__parameters__`` after substitution in some cases because they +are intended primarily for static type checking. + +.. versionchanged:: 3.10 + :class:`Generic` can now be parameterized over parameter expressions. + See :class:`ParamSpec` and :pep:`612` for more details. + A user-defined generic class can have ABCs as base classes without a metaclass conflict. Generic metaclasses are not supported. The outcome of parameterizing generics is cached, and most types in the typing module are hashable and @@ -602,10 +656,80 @@ These can be used as types in annotations using ``[]``, each having a unique syn ``Callable[..., Any]``, and in turn to :class:`collections.abc.Callable`. + Callables which take other callables as arguments may indicate that their + parameter types are dependent on each other using :class:`ParamSpec`. + Additionally, if that callable adds or removes arguments from other + callables, the :data:`Concatenate` operator may be used. They + take the form ``Callable[ParamSpecVariable, ReturnType]`` and + ``Callable[Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable], ReturnType]`` + respectively. + .. deprecated:: 3.9 :class:`collections.abc.Callable` now supports ``[]``. See :pep:`585` and :ref:`types-genericalias`. + .. versionchanged:: 3.10 + ``Callable`` now supports :class:`ParamSpec` and :data:`Concatenate`. + See :pep:`612` for more information. + + .. seealso:: + The documentation for :class:`ParamSpec` and :class:`Concatenate` provide + examples of usage with ``Callable``. + +.. data:: Concatenate + + Used with :data:`Callable` and :class:`ParamSpec` to type annotate a higher + order callable which adds, removes, or transforms parameters of another + callable. Usage is in the form + ``Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable]``. ``Concatenate`` + is currently only valid when used as the first argument to a :data:`Callable`. + The last parameter to ``Concatenate`` must be a :class:`ParamSpec`. + + For example, to annotate a decorator ``with_lock`` which provides a + :class:`threading.Lock` to the decorated function, ``Concatenate`` can be + used to indicate that ``with_lock`` expects a callable which takes in a + ``Lock`` as the first argument, and returns a callable with a different type + signature. In this case, the :class:`ParamSpec` indicates that the returned + callable's parameter types are dependent on the parameter types of the + callable being passed in:: + + from collections.abc import Callable + from threading import Lock + from typing import Any, Concatenate, ParamSpec + + P = ParamSpec('P') + R = ParamSpec('R') + + # Use this lock to ensure that only one thread is executing a function + # at any time. + my_lock = Lock() + + def with_lock(f: Callable[Concatenate[Lock, P], R]) -> Callable[P, R]: + '''A type-safe decorator which provides a lock.''' + global my_lock + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + # Provide the lock as the first argument. + return f(my_lock, *args, **kwargs) + return inner + + @with_lock + def sum_threadsafe(lock: Lock, numbers: list[float]) -> float: + '''Add a list of numbers together in a thread-safe manner.''' + with lock: + return sum(numbers) + + # We don't need to pass in the lock ourselves thanks to the decorator. + sum_threadsafe([1.1, 2.2, 3.3]) + +.. versionadded:: 3.10 + +.. seealso:: + + * :pep:`612` -- Parameter Specification Variables (the PEP which introduced + ``ParamSpec`` and ``Concatenate``). + * :class:`ParamSpec` and :class:`Callable`. + + .. class:: Type(Generic[CT_co]) A variable annotated with ``C`` may accept a value of type ``C``. In @@ -876,6 +1000,84 @@ These are not used in annotations. They are building blocks for creating generic for the type variable must be a subclass of the boundary type, see :pep:`484`. +.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False) + + Parameter specification variable. A specialized version of + :class:`type variables `. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable -- a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or as the first argument to ``Callable``, or as parameters for user-defined + Generics. See :class:`Generic` for more information on generic types. + + For example, to add basic logging to a function, one can create a decorator + ``add_logging`` to log function calls. The parameter specification variable + tells the type checker that the callable passed into the decorator and the + new callable returned by it have inter-dependent type parameters:: + + from collections.abc import Callable + from typing import TypeVar, ParamSpec + import logging + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Without ``ParamSpec``, the simplest way to annotate this previously was to + use a :class:`TypeVar` with bound ``Callable[..., Any]``. However this + causes two problems: + + 1. The type checker can't type check the ``inner`` function because + ``*args`` and ``**kwargs`` have to be typed :data:`Any`. + 2. :func:`~cast` may be required in the body of the ``add_logging`` + decorator when returning the ``inner`` function, or the static type + checker must be told to ignore the ``return inner``. + + .. attribute:: args + .. attribute:: kwargs + + Since ``ParamSpec`` captures both positional and keyword parameters, + ``P.args`` and ``P.kwargs`` can be used to split a ``ParamSpec`` into its + components. ``P.args`` represents the tuple of positional parameters in a + given call and should only be used to annotate ``*args``. ``P.kwargs`` + represents the mapping of keyword parameters to their values in a given call, + and should be only be used to annotate ``**kwargs`` or ``**kwds``. Both + attributes require the annotated parameter to be in scope. + + Parameter specification variables created with ``covariant=True`` or + ``contravariant=True`` can be used to declare covariant or contravariant + generic types. The ``bound`` argument is also accepted, similar to + :class:`TypeVar`. However the actual semantics of these keywords are yet to + be decided. + + .. versionadded:: 3.10 + + .. note:: + Only parameter specification variables defined in global scope can + be pickled. + + .. seealso:: + * :pep:`612` -- Parameter Specification Variables (the PEP which introduced + ``ParamSpec`` and ``Concatenate``). + * :class:`Callable` and :class:`Concatenate`. + .. data:: AnyStr ``AnyStr`` is a type variable defined as diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index aa547ff4648..be529d75e13 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -144,6 +144,28 @@ See :pep:`604` for more details. (Contributed by Maggie Moss and Philippe Prados in :issue:`41428`.) +PEP 612: Parameter Specification Variables +------------------------------------------ + +Two new options to improve the information provided to static type checkers for +:pep:`484`\ 's ``Callable`` have been added to the :mod:`typing` module. + +The first is the parameter specification variable. They are used to forward the +parameter types of one callable to another callable -- a pattern commonly +found in higher order functions and decorators. Examples of usage can be found +in :class:`typing.ParamSpec`. Previously, there was no easy way to type annotate +dependency of parameter types in such a precise manner. + +The second option is the new ``Concatenate`` operator. It's used in conjunction +with parameter specification variables to type annotate a higher order callable +which adds or removes parameters of another callable. Examples of usage can +be found in :class:`typing.Concatenate`. + +See :class:`typing.Callable`, :class:`typing.ParamSpec`, +:class:`typing.Concatenate` and :pep:`612` for more details. + +(Contributed by Ken Jin in :issue:`41559`.) + Other Language Changes ====================== diff --git a/Lib/typing.py b/Lib/typing.py index b140b0e6696..88d0d623a42 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -544,8 +544,9 @@ def TypeAlias(self, parameters): @_SpecialForm def Concatenate(self, parameters): - """Used in conjunction with ParamSpec and Callable to represent a higher - order function which adds, removes or transforms parameters of a Callable. + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. For example:: @@ -735,11 +736,11 @@ class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True): Parameter specification variables exist primarily for the benefit of static type checkers. They are used to forward the parameter types of one - Callable to another Callable, a pattern commonly found in higher order - functions and decorators. They are only valid when used in Concatenate, or - as the first argument to Callable, or as parameters for user-defined Generics. - See class Generic for more information on generic types. An example for - annotating a decorator:: + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``, or as parameters for user-defined + Generics. See class Generic for more information on generic types. An + example for annotating a decorator:: T = TypeVar('T') P = ParamSpec('P')