Skip to content

Commit 5224336

Browse files
bpo-43783: Add ParamSpecArgs/Kwargs (GH-25298)
1 parent 750f484 commit 5224336

File tree

5 files changed

+91
-11
lines changed

5 files changed

+91
-11
lines changed

Doc/library/typing.rst

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,8 +1058,10 @@ These are not used in annotations. They are building blocks for creating generic
10581058
components. ``P.args`` represents the tuple of positional parameters in a
10591059
given call and should only be used to annotate ``*args``. ``P.kwargs``
10601060
represents the mapping of keyword parameters to their values in a given call,
1061-
and should be only be used to annotate ``**kwargs`` or ``**kwds``. Both
1062-
attributes require the annotated parameter to be in scope.
1061+
and should be only be used to annotate ``**kwargs``. Both
1062+
attributes require the annotated parameter to be in scope. At runtime,
1063+
``P.args`` and ``P.kwargs`` are instances respectively of
1064+
:class:`ParamSpecArgs` and :class:`ParamSpecKwargs`.
10631065

10641066
Parameter specification variables created with ``covariant=True`` or
10651067
``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
10781080
``ParamSpec`` and ``Concatenate``).
10791081
* :class:`Callable` and :class:`Concatenate`.
10801082

1083+
.. data:: ParamSpecArgs
1084+
.. data:: ParamSpecKwargs
1085+
1086+
Arguments and keyword arguments attributes of a :class:`ParamSpec`. The
1087+
``P.args`` attribute of a ``ParamSpec`` is an instance of ``ParamSpecArgs``,
1088+
and ``P.kwargs`` is an instance of ``ParamSpecKwargs``. They are intended
1089+
for runtime introspection and have no special meaning to static type checkers.
1090+
1091+
Calling :func:`get_origin` on either of these objects will return the
1092+
original ``ParamSpec``::
1093+
1094+
P = ParamSpec("P")
1095+
get_origin(P.args) # returns P
1096+
get_origin(P.kwargs) # returns P
1097+
1098+
.. versionadded:: 3.10
1099+
1100+
10811101
.. data:: AnyStr
10821102

10831103
``AnyStr`` is a type variable defined as

Doc/whatsnew/3.10.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,9 +550,11 @@ which adds or removes parameters of another callable. Examples of usage can
550550
be found in :class:`typing.Concatenate`.
551551
552552
See :class:`typing.Callable`, :class:`typing.ParamSpec`,
553-
:class:`typing.Concatenate` and :pep:`612` for more details.
553+
:class:`typing.Concatenate`, :class:`typing.ParamSpecArgs`,
554+
:class:`typing.ParamSpecKwargs`, and :pep:`612` for more details.
554555
555-
(Contributed by Ken Jin in :issue:`41559`.)
556+
(Contributed by Ken Jin in :issue:`41559`, with minor enhancements by Jelle
557+
Zijlstra in :issue:`43783`. PEP written by Mark Mendoza.)
556558
557559
558560
PEP 613: TypeAlias Annotation

Lib/test/test_typing.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from typing import Pattern, Match
2626
from typing import Annotated, ForwardRef
2727
from typing import TypeAlias
28-
from typing import ParamSpec, Concatenate
28+
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
2929
import abc
3030
import typing
3131
import weakref
@@ -3004,6 +3004,7 @@ def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]":
30043004
class GetUtilitiesTestCase(TestCase):
30053005
def test_get_origin(self):
30063006
T = TypeVar('T')
3007+
P = ParamSpec('P')
30073008
class C(Generic[T]): pass
30083009
self.assertIs(get_origin(C[int]), C)
30093010
self.assertIs(get_origin(C[T]), C)
@@ -3022,6 +3023,8 @@ class C(Generic[T]): pass
30223023
self.assertIs(get_origin(list[int]), list)
30233024
self.assertIs(get_origin(list), None)
30243025
self.assertIs(get_origin(list | str), types.Union)
3026+
self.assertIs(get_origin(P.args), P)
3027+
self.assertIs(get_origin(P.kwargs), P)
30253028

30263029
def test_get_args(self):
30273030
T = TypeVar('T')
@@ -4265,11 +4268,16 @@ def test_valid_uses(self):
42654268
self.assertEqual(C4.__args__, (P, T))
42664269
self.assertEqual(C4.__parameters__, (P, T))
42674270

4268-
# ParamSpec instances should also have args and kwargs attributes.
4271+
def test_args_kwargs(self):
4272+
P = ParamSpec('P')
42694273
self.assertIn('args', dir(P))
42704274
self.assertIn('kwargs', dir(P))
4271-
P.args
4272-
P.kwargs
4275+
self.assertIsInstance(P.args, ParamSpecArgs)
4276+
self.assertIsInstance(P.kwargs, ParamSpecKwargs)
4277+
self.assertIs(P.args.__origin__, P)
4278+
self.assertIs(P.kwargs.__origin__, P)
4279+
self.assertEqual(repr(P.args), "P.args")
4280+
self.assertEqual(repr(P.kwargs), "P.kwargs")
42734281

42744282
def test_user_generics(self):
42754283
T = TypeVar("T")

Lib/typing.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@
114114
'no_type_check_decorator',
115115
'NoReturn',
116116
'overload',
117+
'ParamSpecArgs',
118+
'ParamSpecKwargs',
117119
'runtime_checkable',
118120
'Text',
119121
'TYPE_CHECKING',
@@ -727,6 +729,44 @@ def __init__(self, name, *constraints, bound=None,
727729
self.__module__ = def_mod
728730

729731

732+
class ParamSpecArgs(_Final, _Immutable, _root=True):
733+
"""The args for a ParamSpec object.
734+
735+
Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.
736+
737+
ParamSpecArgs objects have a reference back to their ParamSpec:
738+
739+
P.args.__origin__ is P
740+
741+
This type is meant for runtime introspection and has no special meaning to
742+
static type checkers.
743+
"""
744+
def __init__(self, origin):
745+
self.__origin__ = origin
746+
747+
def __repr__(self):
748+
return f"{self.__origin__.__name__}.args"
749+
750+
751+
class ParamSpecKwargs(_Final, _Immutable, _root=True):
752+
"""The kwargs for a ParamSpec object.
753+
754+
Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.
755+
756+
ParamSpecKwargs objects have a reference back to their ParamSpec:
757+
758+
P.kwargs.__origin__ is P
759+
760+
This type is meant for runtime introspection and has no special meaning to
761+
static type checkers.
762+
"""
763+
def __init__(self, origin):
764+
self.__origin__ = origin
765+
766+
def __repr__(self):
767+
return f"{self.__origin__.__name__}.kwargs"
768+
769+
730770
class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True):
731771
"""Parameter specification variable.
732772
@@ -776,8 +816,13 @@ def add_two(x: float, y: float) -> float:
776816
__slots__ = ('__name__', '__bound__', '__covariant__', '__contravariant__',
777817
'__dict__')
778818

779-
args = object()
780-
kwargs = object()
819+
@property
820+
def args(self):
821+
return ParamSpecArgs(self)
822+
823+
@property
824+
def kwargs(self):
825+
return ParamSpecKwargs(self)
781826

782827
def __init__(self, name, *, bound=None, covariant=False, contravariant=False):
783828
self.__name__ = name
@@ -1662,10 +1707,12 @@ def get_origin(tp):
16621707
get_origin(Generic[T]) is Generic
16631708
get_origin(Union[T, int]) is Union
16641709
get_origin(List[Tuple[T, T]][int]) == list
1710+
get_origin(P.args) is P
16651711
"""
16661712
if isinstance(tp, _AnnotatedAlias):
16671713
return Annotated
1668-
if isinstance(tp, (_BaseGenericAlias, GenericAlias)):
1714+
if isinstance(tp, (_BaseGenericAlias, GenericAlias,
1715+
ParamSpecArgs, ParamSpecKwargs)):
16691716
return tp.__origin__
16701717
if tp is Generic:
16711718
return Generic
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The ``P.args`` and ``P.kwargs`` attributes of :class:`typing.ParamSpec` are
2+
now instances of the new classes :class:`typing.ParamSpecArgs` and
3+
:class:`typing.ParamSpecKwargs`, which enables a more useful ``repr()``. Patch by Jelle Zijlstra.

0 commit comments

Comments
 (0)