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.