diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 7884308a333020..458f8d4d3f4816 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -765,6 +765,20 @@ function. .. versionadded:: 3.10 ``globalns`` and ``localns`` parameters. + .. classmethod:: Signature.from_code(co) + + Return a :class:`Signature` (or its subclass) object + for a given :class:`code object ` *co*. + + .. note:: + + Default values and annotations + will not be included in the signature, + since code objects have no knowledge of such things. + It is recommended to use :meth:`Signature.from_callable` + when a function object is available, it does not have these limitations. + + .. versionadded:: 3.13 .. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty) diff --git a/Lib/inspect.py b/Lib/inspect.py index c8211833dd0831..a8c176869236ce 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2416,20 +2416,40 @@ def _signature_from_function(cls, func, skip_bound_arg=True, s = getattr(func, "__text_signature__", None) if s: return _signature_fromstr(cls, func, s, skip_bound_arg) - + return _signature_from_code(cls, + func.__code__, + globals=globals, + locals=locals, + eval_str=eval_str, + is_duck_function=is_duck_function, + func=func) + + +def _signature_from_code(cls, + func_code, + globals=None, + locals=None, + eval_str=False, + is_duck_function=False, + func=None): + """Private helper function to get signature for code objects.""" Parameter = cls._parameter_cls # Parameter information. - func_code = func.__code__ pos_count = func_code.co_argcount arg_names = func_code.co_varnames posonly_count = func_code.co_posonlyargcount positional = arg_names[:pos_count] keyword_only_count = func_code.co_kwonlyargcount keyword_only = arg_names[pos_count:pos_count + keyword_only_count] - annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str) - defaults = func.__defaults__ - kwdefaults = func.__kwdefaults__ + if func is not None: + annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str) + defaults = func.__defaults__ + kwdefaults = func.__kwdefaults__ + else: + annotations = {} + defaults = None + kwdefaults = None if defaults: pos_default_count = len(defaults) @@ -3107,6 +3127,17 @@ def from_callable(cls, obj, *, follow_wrapper_chains=follow_wrapped, globals=globals, locals=locals, eval_str=eval_str) + @classmethod + def from_code(cls, co): + """Constructs Signature for the given code object. + + Signatures created from code objects cannot know about annotations + or default values. + """ + if not iscode(co): + raise TypeError(f'code object was expected, got {type(co).__name__!r}') + return _signature_from_code(cls, co) + @property def parameters(self): return self._parameters diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 78ef817906b2aa..d9df28b38297f6 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -3674,6 +3674,43 @@ def test_signature_on_noncallable_mocks(self): with self.assertRaises(TypeError): inspect.signature(mock) + def test_signature_from_code(self): + def func1( + a, b: int, + /, + c, d: str = 'a', + *args: int, + e, f: bool = True, + **kwargs: str, + ) -> float: + ... + + def func2(a: str, b: int = 0, /): ... + def func3(): ... + def func4(*a, **k): ... + def func5(*, kw=False): ... + + known_sigs = { + func1: '(a, b, /, c, d, *args, e, f, **kwargs)', + func2: '(a, b, /)', + func3: '()', + func4: '(*a, **k)', + func5: '(*, kw)', + } + + for test_func, expected_sig in known_sigs.items(): + with self.subTest(test_func=test_func, expected_sig=expected_sig): + self.assertEqual( + str(inspect.Signature.from_code(test_func.__code__)), + expected_sig, + ) + + with self.assertRaisesRegex( + TypeError, + "code object was expected, got 'int'", + ): + inspect.Signature.from_code(1) + def test_signature_equality(self): def foo(a, *, b:int) -> float: pass self.assertFalse(inspect.signature(foo) == 42) diff --git a/Misc/NEWS.d/next/Library/2023-09-05-13-23-06.gh-issue-108901.2KcZab.rst b/Misc/NEWS.d/next/Library/2023-09-05-13-23-06.gh-issue-108901.2KcZab.rst new file mode 100644 index 00000000000000..438adfb47847b3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-09-05-13-23-06.gh-issue-108901.2KcZab.rst @@ -0,0 +1,2 @@ +Add :meth:`inspect.Signature.from_code` to be able +to construct :class:`inspect.Signature` objects from :class:`types.CodeType`.