https://github.com/python/cpython/commit/8ba1f31def0d79f230728fbf0fe400755c0856c2 commit: 8ba1f31def0d79f230728fbf0fe400755c0856c2 branch: main author: Pieter Eendebak <[email protected]> committer: eendebakpt <[email protected]> date: 2026-10-02T22:57:12+02:00 summary:
gh-150816: Speed up inspect.signature() for Python functions (#157463) * Speed up inspect.signature() for Python functions For real Python functions the parameter kinds and defaults come straight from the code object, so the validation done by Parameter.__init__ is redundant. Construct the Parameter objects directly, falling back to the regular constructor for names that are not plain identifiers (e.g. the ".0" implicit argument of comprehensions). Parameter subclasses and duck-typed functions keep the full validation. inspect.signature() on a function with 7 parameters: 23.4 us -> 14.5 us. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * Make the fast Parameter constructor a classmethod and add tests Move the helper to Parameter._from_code(). Add tests that a Signature subclass overriding _parameter_cls still gets its own Parameter class (the concern raised on gh-150823), and that parameter names of function-like objects are still validated. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5 (1M context) <[email protected]> files: A Misc/NEWS.d/next/Library/2026-09-13-21-45-00.gh-issue-150816.pQ3kZs.rst M Lib/inspect.py M Lib/test/test_inspect/test_inspect.py diff --git a/Lib/inspect.py b/Lib/inspect.py index 3683f8c3fd53308..bceed35a53abba1 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2348,6 +2348,8 @@ def _signature_from_function(cls, func, skip_bound_arg=True, return _signature_fromstr(cls, func, s, skip_bound_arg) Parameter = cls._parameter_cls + if Parameter is Signature._parameter_cls and not is_duck_function: + Parameter = Parameter._from_code # Parameter information. func_code = func.__code__ @@ -2746,6 +2748,18 @@ def __init__(self, name, kind, *, default=_empty, annotation=_empty): self._name = name + @classmethod + def _from_code(cls, name, kind, *, default=_empty, annotation=_empty): + # Fast path for Python functions: only the name needs validation. + if iskeyword(name) or not name.isidentifier(): + return cls(name, kind, default=default, annotation=annotation) + self = object.__new__(cls) + self._name = name + self._kind = kind + self._default = default + self._annotation = annotation + return self + def __reduce__(self): return (type(self), (self._name, self._kind), diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 38c7a9bde823d9d..3f14a9e66226eaa 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -3556,6 +3556,48 @@ def __init__(self, marker): self.assertEqual(str(inspect.signature(funclike)), '(marker)') + @cpython_only + def test_signature_functionlike_invalid_names(self): + # The code object of a function-like object is not guaranteed + # to have valid parameter names, so they must be validated. + def func(a, b): + pass + + class funclike: + __name__ = func.__name__ + __code__ = func.__code__.replace(co_varnames=('a', '$b')) + __annotations__ = {} + __defaults__ = None + __kwdefaults__ = None + + def __call__(self, *args): + pass + + with self.assertRaisesRegex(ValueError, + 'is not a valid parameter name'): + inspect.signature(funclike()) + + def test_signature_parameter_cls_subclass(self): + # A Signature subclass can override _parameter_cls with a + # Parameter subclass that has its own constructor. + class MyParameter(inspect.Parameter): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.extra = 'spam' + + class MySignature(inspect.Signature): + _parameter_cls = MyParameter + + def f(a, /, b=1, *args, c, d=2, **kwargs): + pass + + sig = MySignature.from_callable(f) + self.assertEqual(len(sig.parameters), 6) + for param in sig.parameters.values(): + self.assertIs(type(param), MyParameter) + self.assertEqual(param.extra, 'spam') + self.assertEqual(sig, inspect.signature(f)) + def test_signature_on_method(self): class Test: def __init__(*args): @@ -5656,6 +5698,20 @@ def test_signature_parameter_implicit(self): self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_ONLY) self.assertEqual(param.name, 'implicit0') + @cpython_only + def test_signature_from_code_unusual_names(self): + def f(a, b): pass + f.__code__ = f.__code__.replace(co_varnames=('.0', 'b')) + sig = inspect.signature(f) + self.assertEqual(list(sig.parameters), ['implicit0', 'b']) + self.assertEqual(sig.parameters['implicit0'].kind, + inspect.Parameter.POSITIONAL_ONLY) + + f.__code__ = f.__code__.replace(co_varnames=('if', 'b')) + with self.assertRaisesRegex(ValueError, + 'is not a valid parameter name'): + inspect.signature(f) + def test_signature_parameter_immutability(self): p = inspect.Parameter('spam', kind=inspect.Parameter.KEYWORD_ONLY) diff --git a/Misc/NEWS.d/next/Library/2026-09-13-21-45-00.gh-issue-150816.pQ3kZs.rst b/Misc/NEWS.d/next/Library/2026-09-13-21-45-00.gh-issue-150816.pQ3kZs.rst new file mode 100644 index 000000000000000..4f67195f45fc6dc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-13-21-45-00.gh-issue-150816.pQ3kZs.rst @@ -0,0 +1,2 @@ +Speed up :func:`inspect.signature` for Python functions by skipping +redundant validation when creating :class:`inspect.Parameter` objects. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
