From 290e1ad38ec290fac5866169584c25f41f4a273c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 10 Mar 2024 14:51:23 +0300 Subject: [PATCH 1/9] gh-108901: Add `skip_bound_arg` to `Signature.from_callable()` and `signature()` --- Doc/library/inspect.rst | 11 +++- Doc/whatsnew/3.13.rst | 8 +++ Lib/inspect.py | 8 ++- Lib/test/test_inspect/test_inspect.py | 59 +++++++++++++++++++ ...-03-10-14-45-00.gh-issue-108901.vMlWej.rst | 3 + 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index ed8d705da3b0b5..460e0dcc198847 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -641,7 +641,7 @@ and its return annotation. To retrieve a :class:`!Signature` object, use the :func:`!signature` function. -.. function:: signature(callable, *, follow_wrapped=True, globals=None, locals=None, eval_str=False) +.. function:: signature(callable, *, follow_wrapped=True, skip_bound_arg=True, globals=None, locals=None, eval_str=False) Return a :class:`Signature` object for the given *callable*: @@ -693,6 +693,10 @@ function. .. versionchanged:: 3.10 The *globals*, *locals*, and *eval_str* parameters were added. + .. versionchanged:: 3.13 + The *skip_bound_arg* parameter was added. + Pass ``False`` to keep the ``self`` parameter in a signature. + .. note:: Some callables may not be introspectable in certain implementations of @@ -796,7 +800,7 @@ function. .. versionadded:: 3.13 - .. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False) + .. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, skip_bound_arg=True, globals=None, locals=None, eval_str=False) Return a :class:`Signature` (or its subclass) object for a given callable *obj*. @@ -817,6 +821,9 @@ function. .. versionchanged:: 3.10 The *globals*, *locals*, and *eval_str* parameters were added. + .. versionchanged:: 3.13 + The *skip_bound_arg* parameter was added. + .. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 51939909000960..c868793c255f62 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -361,6 +361,14 @@ and only logged in :ref:`Python Development Mode ` or on :ref:`Python built on debug mode `. (Contributed by Victor Stinner in :gh:`62948`.) +inspect +------- + +* Add *skip_bound_arg* parameter to :func:`inspect.Signature.from_callable` + and :func:`inspect.signature`, pass ``False`` + to keep the ``self`` parameter in a signature. + (Contributed by Nikita Sobolev in :gh:`108901`.) + ipaddress --------- diff --git a/Lib/inspect.py b/Lib/inspect.py index 8a2b2c96e993b5..ac589341d42396 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3087,10 +3087,12 @@ def __init__(self, parameters=None, *, return_annotation=_empty, @classmethod def from_callable(cls, obj, *, - follow_wrapped=True, globals=None, locals=None, eval_str=False): + follow_wrapped=True, skip_bound_arg=True, + globals=None, locals=None, eval_str=False): """Constructs Signature for the given callable object.""" return _signature_from_callable(obj, sigcls=cls, follow_wrapper_chains=follow_wrapped, + skip_bound_arg=skip_bound_arg, globals=globals, locals=locals, eval_str=eval_str) @property @@ -3357,9 +3359,11 @@ def format(self, *, max_width=None): return rendered -def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False): +def signature(obj, *, follow_wrapped=True, skip_bound_arg=True, + globals=None, locals=None, eval_str=False): """Get a signature object for the passed callable.""" return Signature.from_callable(obj, follow_wrapped=follow_wrapped, + skip_bound_arg=skip_bound_arg, globals=globals, locals=locals, eval_str=eval_str) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 52cf68b93b85fa..ad2ddeb18582b0 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4622,6 +4622,65 @@ class D2(D1): self.assertEqual(inspect.signature(D2), inspect.signature(D1)) + def test_signature_skip_bound_arg_method(self): + def decorator(func): + @functools.wraps(func) # set `__wrapper__` attribute + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + + class My: + def method(self, arg: int) -> None: ... + @decorator + def decorated(self, arg: int) -> None: ... + @classmethod + def cl(cls, arg2: str) -> None: ... + @staticmethod + def st(arg1: str) -> bool: ... + + for follow_wrapped in (True, False): + sigs = { + My.method: '(self, arg: int) -> None', + My().method: '(self, arg: int) -> None', + My.decorated: ( + '(self, arg: int) -> None' + if follow_wrapped else + '(*args, **kwargs) -> None' + ), + My().decorated: ( + '(self, arg: int) -> None' + if follow_wrapped else + '(*args, **kwargs) -> None' + ), + My.cl: '(cls, arg2: str) -> None', + My().cl: '(cls, arg2: str) -> None', + My.st: '(arg1: str) -> bool', + My().st: '(arg1: str) -> bool', + } + + for func in (inspect.signature, inspect.Signature.from_callable): + for fixture, text_sig in sigs.items(): + with self.subTest( + fixture=fixture, + func=func, + follow_wrapped=follow_wrapped, + ): + sig = func( + fixture, + follow_wrapped=follow_wrapped, + skip_bound_arg=False, + ) + self.assertEqual(str(sig), text_sig) + + def test_signature_skip_bound_arg_function(self): + def compare(self: object, other: object) -> bool: ... + + for func in (inspect.signature, inspect.Signature.from_callable): + with self.subTest(func=func): + sig = func(compare, skip_bound_arg=False) + self.assertEqual(str(sig), + '(self: object, other: object) -> bool') + class TestParameterObject(unittest.TestCase): def test_signature_parameter_kinds(self): diff --git a/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst b/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst new file mode 100644 index 00000000000000..c8ab0d953078b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst @@ -0,0 +1,3 @@ +Add *skip_bound_arg* keyword-only parameter to +:func:`inspect.Signature.from_callable` and :func:`inspect.signature`. Pass +``skip_bound_arg=False`` to keep the ``self`` parameter in a signature. From 2e36181066e095870a84336e2d6cbb6bf5cd17da Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sun, 10 Mar 2024 19:37:16 +0300 Subject: [PATCH 2/9] Update Doc/whatsnew/3.13.rst Co-authored-by: Victor Stinner --- Doc/whatsnew/3.13.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index c868793c255f62..c3ea517d52d575 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -365,8 +365,8 @@ inspect ------- * Add *skip_bound_arg* parameter to :func:`inspect.Signature.from_callable` - and :func:`inspect.signature`, pass ``False`` - to keep the ``self`` parameter in a signature. + and :func:`inspect.signature`: keep the ``self`` parameter + in the signature if *skip_bound_arg* is False. (Contributed by Nikita Sobolev in :gh:`108901`.) ipaddress From a0ed11620150c9074c8d0c4163552a71a2362796 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 10 Mar 2024 19:42:23 +0300 Subject: [PATCH 3/9] Address review --- Doc/library/inspect.rst | 2 +- Lib/test/test_inspect/test_inspect.py | 6 ++++-- .../Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 460e0dcc198847..934aa07d944e4e 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -695,7 +695,7 @@ function. .. versionchanged:: 3.13 The *skip_bound_arg* parameter was added. - Pass ``False`` to keep the ``self`` parameter in a signature. + If *skip_bound_arg* is ``False``, keep ``self`` parameter in a signature. .. note:: diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index ad2ddeb18582b0..279ec6b48ac811 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4677,9 +4677,11 @@ def compare(self: object, other: object) -> bool: ... for func in (inspect.signature, inspect.Signature.from_callable): with self.subTest(func=func): - sig = func(compare, skip_bound_arg=False) - self.assertEqual(str(sig), + sig1 = func(compare, skip_bound_arg=False) + sig2 = func(compare, skip_bound_arg=True) + self.assertEqual(str(sig1), '(self: object, other: object) -> bool') + self.assertEqual(sig1, sig2) class TestParameterObject(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst b/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst index c8ab0d953078b0..f785592a0e5e0f 100644 --- a/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst +++ b/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst @@ -1,3 +1,3 @@ Add *skip_bound_arg* keyword-only parameter to -:func:`inspect.Signature.from_callable` and :func:`inspect.signature`. Pass -``skip_bound_arg=False`` to keep the ``self`` parameter in a signature. +:func:`inspect.Signature.from_callable` and :func:`inspect.signature`. +If *skip_bound_arg* is ``False``, keep ``self`` parameter in a signature. From 470625f03633350aa6ace16ae59b7e7590c05c0b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 10 Mar 2024 19:50:40 +0300 Subject: [PATCH 4/9] Update docs --- Doc/library/inspect.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 934aa07d944e4e..85791b6da5e613 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -665,6 +665,11 @@ function. Accepts a wide range of Python callables, from plain functions and classes to :func:`functools.partial` objects. + If *follow_wrapped* is ``False`` *callable* will not be unwrapped + (``callable.__wrapped__`` will not be used to unwrap decorated callables). + + If *skip_bound_arg* is ``False``, keep ``self`` parameter in a signature. + For objects defined in modules using stringized annotations (``from __future__ import annotations``), :func:`signature` will attempt to automatically un-stringize the annotations using @@ -686,16 +691,12 @@ function. .. versionchanged:: 3.5 The *follow_wrapped* parameter was added. - Pass ``False`` to get a signature of - *callable* specifically (``callable.__wrapped__`` will not be used to - unwrap decorated callables.) .. versionchanged:: 3.10 The *globals*, *locals*, and *eval_str* parameters were added. .. versionchanged:: 3.13 The *skip_bound_arg* parameter was added. - If *skip_bound_arg* is ``False``, keep ``self`` parameter in a signature. .. note:: From 8f6424a2c4a732cb134c50b33e8d83dd50add12e Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 11 Mar 2024 12:15:22 +0300 Subject: [PATCH 5/9] Show the signature change in test --- Lib/test/test_inspect/test_inspect.py | 67 +++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 279ec6b48ac811..a45e65934aabdd 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4639,7 +4639,7 @@ def cl(cls, arg2: str) -> None: ... def st(arg1: str) -> bool: ... for follow_wrapped in (True, False): - sigs = { + unbound_sigs = { My.method: '(self, arg: int) -> None', My().method: '(self, arg: int) -> None', My.decorated: ( @@ -4654,16 +4654,41 @@ def st(arg1: str) -> bool: ... ), My.cl: '(cls, arg2: str) -> None', My().cl: '(cls, arg2: str) -> None', + } + + bound_sigs = { + My.method: '(self, arg: int) -> None', + My().method: '(arg: int) -> None', + My.decorated: ( + '(self, arg: int) -> None' + if follow_wrapped else + '(*args, **kwargs) -> None' + ), + My().decorated: ( + '(arg: int) -> None' + if follow_wrapped else + '(*args, **kwargs) -> None' + ), + My.cl: '(arg2: str) -> None', + My().cl: '(arg2: str) -> None', + } + + common_sigs = { My.st: '(arg1: str) -> bool', My().st: '(arg1: str) -> bool', } - for func in (inspect.signature, inspect.Signature.from_callable): - for fixture, text_sig in sigs.items(): + for func in ( + inspect.signature, + inspect.Signature.from_callable, + ): + for fixture, text_sig in unbound_sigs.items(): with self.subTest( fixture=fixture, + text_sig=text_sig, func=func, follow_wrapped=follow_wrapped, + sigs='unbound_sigs', ): sig = func( fixture, @@ -4672,6 +4697,42 @@ def st(arg1: str) -> bool: ... ) self.assertEqual(str(sig), text_sig) + for fixture, text_sig in bound_sigs.items(): + with self.subTest( + fixture=fixture, + text_sig=text_sig, + func=func, + follow_wrapped=follow_wrapped, + sigs='bound_sigs', + ): + sig = func( + fixture, + follow_wrapped=follow_wrapped, + skip_bound_arg=True, + ) + self.assertEqual(str(sig), text_sig) + + for fixture, text_sig in common_sigs.items(): + with self.subTest( + fixture=fixture, + text_sig=text_sig, + func=func, + follow_wrapped=follow_wrapped, + sigs='common_sigs', + ): + sig1 = func( + fixture, + follow_wrapped=follow_wrapped, + skip_bound_arg=False, + ) + sig2 = func( + fixture, + follow_wrapped=follow_wrapped, + skip_bound_arg=True, + ) + self.assertEqual(str(sig1), text_sig) + self.assertEqual(sig1, sig2) + def test_signature_skip_bound_arg_function(self): def compare(self: object, other: object) -> bool: ... From 4f08ab7287019eb6e30a2dca8929f9ce5021a728 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 11 Mar 2024 12:17:55 +0300 Subject: [PATCH 6/9] Simply example --- Lib/test/test_inspect/test_inspect.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index a45e65934aabdd..14795286197814 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4640,13 +4640,7 @@ def st(arg1: str) -> bool: ... for follow_wrapped in (True, False): unbound_sigs = { - My.method: '(self, arg: int) -> None', My().method: '(self, arg: int) -> None', - My.decorated: ( - '(self, arg: int) -> None' - if follow_wrapped else - '(*args, **kwargs) -> None' - ), My().decorated: ( '(self, arg: int) -> None' if follow_wrapped else @@ -4657,13 +4651,7 @@ def st(arg1: str) -> bool: ... } bound_sigs = { - My.method: '(self, arg: int) -> None', My().method: '(arg: int) -> None', - My.decorated: ( - '(self, arg: int) -> None' - if follow_wrapped else - '(*args, **kwargs) -> None' - ), My().decorated: ( '(arg: int) -> None' if follow_wrapped else @@ -4674,6 +4662,12 @@ def st(arg1: str) -> bool: ... } common_sigs = { + My.method: '(self, arg: int) -> None', + My.decorated: ( + '(self, arg: int) -> None' + if follow_wrapped else + '(*args, **kwargs) -> None' + ), My.st: '(arg1: str) -> bool', My().st: '(arg1: str) -> bool', } From d76ba71fa088027089c41d3d32ca598f1a1d2642 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 11 Mar 2024 13:13:13 +0300 Subject: [PATCH 7/9] Improve readability --- Lib/test/test_inspect/test_inspect.py | 138 +++++++++++--------------- 1 file changed, 56 insertions(+), 82 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 14795286197814..df23c5604f2751 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4622,7 +4622,8 @@ class D2(D1): self.assertEqual(inspect.signature(D2), inspect.signature(D1)) - def test_signature_skip_bound_arg_method(self): + + def check_bound_arg(self, *, skip_bound_arg, follow_wrapped): def decorator(func): @functools.wraps(func) # set `__wrapper__` attribute def wrapper(*args, **kwargs): @@ -4638,94 +4639,67 @@ def cl(cls, arg2: str) -> None: ... @staticmethod def st(arg1: str) -> bool: ... - for follow_wrapped in (True, False): - unbound_sigs = { - My().method: '(self, arg: int) -> None', - My().decorated: ( - '(self, arg: int) -> None' - if follow_wrapped else - '(*args, **kwargs) -> None' - ), - My.cl: '(cls, arg2: str) -> None', - My().cl: '(cls, arg2: str) -> None', - } - - bound_sigs = { - My().method: '(arg: int) -> None', - My().decorated: ( + if skip_bound_arg: + signatures = [ + (My().method, '(arg: int) -> None'), + (My().decorated, ( '(arg: int) -> None' if follow_wrapped else '(*args, **kwargs) -> None' - ), - My.cl: '(arg2: str) -> None', - My().cl: '(arg2: str) -> None', - } - - common_sigs = { - My.method: '(self, arg: int) -> None', - My.decorated: ( + )), + (My.cl, '(arg2: str) -> None'), + (My().cl, '(arg2: str) -> None'), + ] + else: + signatures = [ + (My().method, '(self, arg: int) -> None'), + (My().decorated, ( '(self, arg: int) -> None' if follow_wrapped else '(*args, **kwargs) -> None' - ), - My.st: '(arg1: str) -> bool', - My().st: '(arg1: str) -> bool', - } - - for func in ( - inspect.signature, - inspect.Signature.from_callable, - ): - for fixture, text_sig in unbound_sigs.items(): - with self.subTest( - fixture=fixture, - text_sig=text_sig, - func=func, - follow_wrapped=follow_wrapped, - sigs='unbound_sigs', - ): - sig = func( - fixture, - follow_wrapped=follow_wrapped, - skip_bound_arg=False, - ) - self.assertEqual(str(sig), text_sig) - - for fixture, text_sig in bound_sigs.items(): - with self.subTest( - fixture=fixture, - text_sig=text_sig, - func=func, - follow_wrapped=follow_wrapped, - sigs='bound_sigs', - ): - sig = func( - fixture, - follow_wrapped=follow_wrapped, - skip_bound_arg=True, - ) - self.assertEqual(str(sig), text_sig) - - for fixture, text_sig in common_sigs.items(): - with self.subTest( - fixture=fixture, - text_sig=text_sig, - func=func, + )), + (My.cl, '(cls, arg2: str) -> None'), + (My().cl, '(cls, arg2: str) -> None'), + ] + + common_signatures = [ + (My.method, '(self, arg: int) -> None'), + (My.decorated, ( + '(self, arg: int) -> None' + if follow_wrapped else + '(*args, **kwargs) -> None' + )), + (My.st, '(arg1: str) -> bool'), + (My().st, '(arg1: str) -> bool'), + ] + signatures.extend(common_signatures) + + for signature_func in ( + inspect.signature, + inspect.Signature.from_callable, + ): + for callable, text_sig in signatures: + with self.subTest( + callable=callable, + text_sig=text_sig, + signature_func=signature_func, + follow_wrapped=follow_wrapped, + skip_bound_arg=skip_bound_arg, + ): + sig = signature_func( + callable, follow_wrapped=follow_wrapped, - sigs='common_sigs', - ): - sig1 = func( - fixture, - follow_wrapped=follow_wrapped, - skip_bound_arg=False, - ) - sig2 = func( - fixture, - follow_wrapped=follow_wrapped, - skip_bound_arg=True, - ) - self.assertEqual(str(sig1), text_sig) - self.assertEqual(sig1, sig2) + skip_bound_arg=skip_bound_arg, + ) + self.assertEqual(str(sig), text_sig) + + def test_signature_skip_bound_arg_method(self): + for skip_bound_arg in (True, False): + for follow_wrapped in (True, False): + self.check_bound_arg( + skip_bound_arg=skip_bound_arg, + follow_wrapped=follow_wrapped, + ) def test_signature_skip_bound_arg_function(self): def compare(self: object, other: object) -> bool: ... From 4d5a96e265ec8a58d5750c6ca4ea265683518c5d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 24 Apr 2024 16:50:43 +0300 Subject: [PATCH 8/9] Rename `skip_bound_arg` to `bound_arg` --- Doc/library/inspect.rst | 11 ++-- Doc/whatsnew/3.13.rst | 4 +- Lib/inspect.py | 8 +-- Lib/test/test_inspect/test_inspect.py | 51 +++++++++---------- ...-03-10-14-45-00.gh-issue-108901.vMlWej.rst | 4 +- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 381d47ceb42068..cd3d4cc4dd683e 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -651,7 +651,7 @@ and its return annotation. To retrieve a :class:`!Signature` object, use the :func:`!signature` function. -.. function:: signature(callable, *, follow_wrapped=True, skip_bound_arg=True, globals=None, locals=None, eval_str=False) +.. function:: signature(callable, *, follow_wrapped=True, bound_arg=False, globals=None, locals=None, eval_str=False) Return a :class:`Signature` object for the given *callable*: @@ -678,7 +678,8 @@ function. If *follow_wrapped* is ``False`` *callable* will not be unwrapped (``callable.__wrapped__`` will not be used to unwrap decorated callables). - If *skip_bound_arg* is ``False``, keep ``self`` parameter in a signature. + If *bound_arg* is ``False``, remove ``self`` parameter + from the method signature. For objects defined in modules using stringized annotations (``from __future__ import annotations``), :func:`signature` will @@ -706,7 +707,7 @@ function. The *globals*, *locals*, and *eval_str* parameters were added. .. versionchanged:: 3.13 - The *skip_bound_arg* parameter was added. + The *bound_arg* parameter was added. .. note:: @@ -811,7 +812,7 @@ function. .. versionadded:: 3.13 - .. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, skip_bound_arg=True, globals=None, locals=None, eval_str=False) + .. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, bound_arg=False, globals=None, locals=None, eval_str=False) Return a :class:`Signature` (or its subclass) object for a given callable *obj*. @@ -833,7 +834,7 @@ function. The *globals*, *locals*, and *eval_str* parameters were added. .. versionchanged:: 3.13 - The *skip_bound_arg* parameter was added. + The *bound_arg* parameter was added. .. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 4cae01d79ba88c..97f101d0c149fb 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -524,9 +524,9 @@ io inspect ------- -* Add *skip_bound_arg* parameter to :func:`inspect.Signature.from_callable` +* Add *bound_arg* parameter to :func:`inspect.Signature.from_callable` and :func:`inspect.signature`: keep the ``self`` parameter - in the signature if *skip_bound_arg* is False. + in the method signature if *bound_arg* is True. (Contributed by Nikita Sobolev in :gh:`108901`.) ipaddress diff --git a/Lib/inspect.py b/Lib/inspect.py index ccbfa98362e287..c423843cfe05bd 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3085,12 +3085,12 @@ def __init__(self, parameters=None, *, return_annotation=_empty, @classmethod def from_callable(cls, obj, *, - follow_wrapped=True, skip_bound_arg=True, + follow_wrapped=True, bound_arg=False, globals=None, locals=None, eval_str=False): """Constructs Signature for the given callable object.""" return _signature_from_callable(obj, sigcls=cls, follow_wrapper_chains=follow_wrapped, - skip_bound_arg=skip_bound_arg, + skip_bound_arg=not bound_arg, globals=globals, locals=locals, eval_str=eval_str) @property @@ -3357,11 +3357,11 @@ def format(self, *, max_width=None): return rendered -def signature(obj, *, follow_wrapped=True, skip_bound_arg=True, +def signature(obj, *, follow_wrapped=True, bound_arg=False, globals=None, locals=None, eval_str=False): """Get a signature object for the passed callable.""" return Signature.from_callable(obj, follow_wrapped=follow_wrapped, - skip_bound_arg=skip_bound_arg, + bound_arg=bound_arg, globals=globals, locals=locals, eval_str=eval_str) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 6dba3fdd472d84..6efa88df02803b 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4645,13 +4645,17 @@ class D2(D1): self.assertEqual(inspect.signature(D2), inspect.signature(D1)) - def check_bound_arg(self, *, skip_bound_arg, follow_wrapped): + def check_bound_arg(self, *, bound_arg, follow_wrapped): def decorator(func): @functools.wraps(func) # set `__wrapper__` attribute def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper + def func(self, arg: int) -> None: ... + @decorator + def decorated(self, arg: int) -> None: ... + class My: def method(self, arg: int) -> None: ... @decorator @@ -4661,30 +4665,36 @@ def cl(cls, arg2: str) -> None: ... @staticmethod def st(arg1: str) -> bool: ... - if skip_bound_arg: + if bound_arg: signatures = [ - (My().method, '(arg: int) -> None'), + (My().method, '(self, arg: int) -> None'), (My().decorated, ( - '(arg: int) -> None' + '(self, arg: int) -> None' if follow_wrapped else '(*args, **kwargs) -> None' )), - (My.cl, '(arg2: str) -> None'), - (My().cl, '(arg2: str) -> None'), + (My.cl, '(cls, arg2: str) -> None'), + (My().cl, '(cls, arg2: str) -> None'), ] else: signatures = [ - (My().method, '(self, arg: int) -> None'), + (My().method, '(arg: int) -> None'), (My().decorated, ( - '(self, arg: int) -> None' + '(arg: int) -> None' if follow_wrapped else '(*args, **kwargs) -> None' )), - (My.cl, '(cls, arg2: str) -> None'), - (My().cl, '(cls, arg2: str) -> None'), + (My.cl, '(arg2: str) -> None'), + (My().cl, '(arg2: str) -> None'), ] common_signatures = [ + (func, '(self, arg: int) -> None'), + (decorated, ( + '(self, arg: int) -> None' + if follow_wrapped else + '(*args, **kwargs) -> None' + )), (My.method, '(self, arg: int) -> None'), (My.decorated, ( '(self, arg: int) -> None' @@ -4706,34 +4716,23 @@ def st(arg1: str) -> bool: ... text_sig=text_sig, signature_func=signature_func, follow_wrapped=follow_wrapped, - skip_bound_arg=skip_bound_arg, + bound_arg=bound_arg, ): sig = signature_func( callable, follow_wrapped=follow_wrapped, - skip_bound_arg=skip_bound_arg, + bound_arg=bound_arg, ) self.assertEqual(str(sig), text_sig) - def test_signature_skip_bound_arg_method(self): - for skip_bound_arg in (True, False): + def test_signature_bound_arg_method(self): + for bound_arg in (True, False): for follow_wrapped in (True, False): self.check_bound_arg( - skip_bound_arg=skip_bound_arg, + bound_arg=bound_arg, follow_wrapped=follow_wrapped, ) - def test_signature_skip_bound_arg_function(self): - def compare(self: object, other: object) -> bool: ... - - for func in (inspect.signature, inspect.Signature.from_callable): - with self.subTest(func=func): - sig1 = func(compare, skip_bound_arg=False) - sig2 = func(compare, skip_bound_arg=True) - self.assertEqual(str(sig1), - '(self: object, other: object) -> bool') - self.assertEqual(sig1, sig2) - class TestParameterObject(unittest.TestCase): def test_signature_parameter_kinds(self): diff --git a/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst b/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst index f785592a0e5e0f..0e19d2482b5876 100644 --- a/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst +++ b/Misc/NEWS.d/next/Library/2024-03-10-14-45-00.gh-issue-108901.vMlWej.rst @@ -1,3 +1,3 @@ -Add *skip_bound_arg* keyword-only parameter to +Add *bound_arg* keyword-only parameter to :func:`inspect.Signature.from_callable` and :func:`inspect.signature`. -If *skip_bound_arg* is ``False``, keep ``self`` parameter in a signature. +If *bound_arg* is ``True``, keep ``self`` parameter in method a signature. From 3b884ebba607d6f9207e08091326e7248b01f88e Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 24 Apr 2024 16:55:10 +0300 Subject: [PATCH 9/9] Mention `inspect313` --- Doc/whatsnew/3.13.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 97f101d0c149fb..4bfec1dd179437 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -527,6 +527,7 @@ inspect * Add *bound_arg* parameter to :func:`inspect.Signature.from_callable` and :func:`inspect.signature`: keep the ``self`` parameter in the method signature if *bound_arg* is True. + :pypi:`inspect313` package has a backport of this feature. (Contributed by Nikita Sobolev in :gh:`108901`.) ipaddress