summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJelle Zijlstra <jelle.zijlstra@gmail.com>2021-04-11 02:57:05 (GMT)
committerGitHub <noreply@github.com>2021-04-11 02:57:05 (GMT)
commit522433601a5c64603dab3d733f41a5db39d237eb (patch)
tree4ed806539350027b86ff9afc1b780288d91d7fb7
parent750f484752763fe9ac1d6455780aabcb67f25508 (diff)
downloadcpython-522433601a5c64603dab3d733f41a5db39d237eb.zip
cpython-522433601a5c64603dab3d733f41a5db39d237eb.tar.gz
cpython-522433601a5c64603dab3d733f41a5db39d237eb.tar.bz2
bpo-43783: Add ParamSpecArgs/Kwargs (GH-25298)
-rw-r--r--Doc/library/typing.rst24
-rw-r--r--Doc/whatsnew/3.10.rst6
-rw-r--r--Lib/test/test_typing.py16
-rw-r--r--Lib/typing.py53
-rw-r--r--Misc/NEWS.d/next/Library/2021-04-08-19-32-26.bpo-47383.YI1hdL.rst3
5 files changed, 91 insertions, 11 deletions
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 8af57f3..c0c6cdd 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -1058,8 +1058,10 @@ These are not used in annotations. They are building blocks for creating generic
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.
+ and should be only be used to annotate ``**kwargs``. Both
+ attributes require the annotated parameter to be in scope. At runtime,
+ ``P.args`` and ``P.kwargs`` are instances respectively of
+ :class:`ParamSpecArgs` and :class:`ParamSpecKwargs`.
Parameter specification variables created with ``covariant=True`` or
``contravariant=True`` can be used to declare covariant or contravariant
@@ -1078,6 +1080,24 @@ These are not used in annotations. They are building blocks for creating generic
``ParamSpec`` and ``Concatenate``).
* :class:`Callable` and :class:`Concatenate`.
+.. data:: ParamSpecArgs
+.. data:: ParamSpecKwargs
+
+ Arguments and keyword arguments attributes of a :class:`ParamSpec`. The
+ ``P.args`` attribute of a ``ParamSpec`` is an instance of ``ParamSpecArgs``,
+ and ``P.kwargs`` is an instance of ``ParamSpecKwargs``. They are intended
+ for runtime introspection and have no special meaning to static type checkers.
+
+ Calling :func:`get_origin` on either of these objects will return the
+ original ``ParamSpec``::
+
+ P = ParamSpec("P")
+ get_origin(P.args) # returns P
+ get_origin(P.kwargs) # returns P
+
+ .. versionadded:: 3.10
+
+
.. data:: AnyStr
``AnyStr`` is a type variable defined as
diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index 09064ec..50c8d53 100644
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -550,9 +550,11 @@ 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.
+:class:`typing.Concatenate`, :class:`typing.ParamSpecArgs`,
+:class:`typing.ParamSpecKwargs`, and :pep:`612` for more details.
-(Contributed by Ken Jin in :issue:`41559`.)
+(Contributed by Ken Jin in :issue:`41559`, with minor enhancements by Jelle
+Zijlstra in :issue:`43783`. PEP written by Mark Mendoza.)
PEP 613: TypeAlias Annotation
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 3b8efe1..7183686 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -25,7 +25,7 @@ from typing import IO, TextIO, BinaryIO
from typing import Pattern, Match
from typing import Annotated, ForwardRef
from typing import TypeAlias
-from typing import ParamSpec, Concatenate
+from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
import abc
import typing
import weakref
@@ -3004,6 +3004,7 @@ class GetTypeHintTests(BaseTestCase):
class GetUtilitiesTestCase(TestCase):
def test_get_origin(self):
T = TypeVar('T')
+ P = ParamSpec('P')
class C(Generic[T]): pass
self.assertIs(get_origin(C[int]), C)
self.assertIs(get_origin(C[T]), C)
@@ -3022,6 +3023,8 @@ class GetUtilitiesTestCase(TestCase):
self.assertIs(get_origin(list[int]), list)
self.assertIs(get_origin(list), None)
self.assertIs(get_origin(list | str), types.Union)
+ self.assertIs(get_origin(P.args), P)
+ self.assertIs(get_origin(P.kwargs), P)
def test_get_args(self):
T = TypeVar('T')
@@ -4265,11 +4268,16 @@ class ParamSpecTests(BaseTestCase):
self.assertEqual(C4.__args__, (P, T))
self.assertEqual(C4.__parameters__, (P, T))
- # ParamSpec instances should also have args and kwargs attributes.
+ def test_args_kwargs(self):
+ P = ParamSpec('P')
self.assertIn('args', dir(P))
self.assertIn('kwargs', dir(P))
- P.args
- P.kwargs
+ self.assertIsInstance(P.args, ParamSpecArgs)
+ self.assertIsInstance(P.kwargs, ParamSpecKwargs)
+ self.assertIs(P.args.__origin__, P)
+ self.assertIs(P.kwargs.__origin__, P)
+ self.assertEqual(repr(P.args), "P.args")
+ self.assertEqual(repr(P.kwargs), "P.kwargs")
def test_user_generics(self):
T = TypeVar("T")
diff --git a/Lib/typing.py b/Lib/typing.py
index 6224930..6461ba2 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -114,6 +114,8 @@ __all__ = [
'no_type_check_decorator',
'NoReturn',
'overload',
+ 'ParamSpecArgs',
+ 'ParamSpecKwargs',
'runtime_checkable',
'Text',
'TYPE_CHECKING',
@@ -727,6 +729,44 @@ class TypeVar( _Final, _Immutable, _TypeVarLike, _root=True):
self.__module__ = def_mod
+class ParamSpecArgs(_Final, _Immutable, _root=True):
+ """The args for a ParamSpec object.
+
+ Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.
+
+ ParamSpecArgs objects have a reference back to their ParamSpec:
+
+ P.args.__origin__ is P
+
+ This type is meant for runtime introspection and has no special meaning to
+ static type checkers.
+ """
+ def __init__(self, origin):
+ self.__origin__ = origin
+
+ def __repr__(self):
+ return f"{self.__origin__.__name__}.args"
+
+
+class ParamSpecKwargs(_Final, _Immutable, _root=True):
+ """The kwargs for a ParamSpec object.
+
+ Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.
+
+ ParamSpecKwargs objects have a reference back to their ParamSpec:
+
+ P.kwargs.__origin__ is P
+
+ This type is meant for runtime introspection and has no special meaning to
+ static type checkers.
+ """
+ def __init__(self, origin):
+ self.__origin__ = origin
+
+ def __repr__(self):
+ return f"{self.__origin__.__name__}.kwargs"
+
+
class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True):
"""Parameter specification variable.
@@ -776,8 +816,13 @@ class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True):
__slots__ = ('__name__', '__bound__', '__covariant__', '__contravariant__',
'__dict__')
- args = object()
- kwargs = object()
+ @property
+ def args(self):
+ return ParamSpecArgs(self)
+
+ @property
+ def kwargs(self):
+ return ParamSpecKwargs(self)
def __init__(self, name, *, bound=None, covariant=False, contravariant=False):
self.__name__ = name
@@ -1662,10 +1707,12 @@ def get_origin(tp):
get_origin(Generic[T]) is Generic
get_origin(Union[T, int]) is Union
get_origin(List[Tuple[T, T]][int]) == list
+ get_origin(P.args) is P
"""
if isinstance(tp, _AnnotatedAlias):
return Annotated
- if isinstance(tp, (_BaseGenericAlias, GenericAlias)):
+ if isinstance(tp, (_BaseGenericAlias, GenericAlias,
+ ParamSpecArgs, ParamSpecKwargs)):
return tp.__origin__
if tp is Generic:
return Generic
diff --git a/Misc/NEWS.d/next/Library/2021-04-08-19-32-26.bpo-47383.YI1hdL.rst b/Misc/NEWS.d/next/Library/2021-04-08-19-32-26.bpo-47383.YI1hdL.rst
new file mode 100644
index 0000000..8b68006
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-04-08-19-32-26.bpo-47383.YI1hdL.rst
@@ -0,0 +1,3 @@
+The ``P.args`` and ``P.kwargs`` attributes of :class:`typing.ParamSpec` are
+now instances of the new classes :class:`typing.ParamSpecArgs` and
+:class:`typing.ParamSpecKwargs`, which enables a more useful ``repr()``. Patch by Jelle Zijlstra.