Skip to content

gh-150816: Speed up inspect.signature() for Python functions - #157463

Open
eendebakpt wants to merge 3 commits into
python:mainfrom
eendebakpt:inspect-signature-fast-params
Open

eendebakpt wants to merge 3 commits into
python:mainfrom
eendebakpt:inspect-signature-fast-params

Conversation

@eendebakpt

@eendebakpt eendebakpt commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

For a Python function, _signature_from_function() builds every Parameter through the public constructor, which validates the kind, the default and the name. This PR adds a private Parameter._from_code() that fills the slots directly and checks only the name.

This picks up #150823 and addresses its review comments:

  • The fast path is used only when Signature._parameter_cls is Parameter itself. (subclasses tested in test_signature_parameter_cls_subclass).
  • Parameter names are checked for every object, and function-like objects keep using the regular constructor, so invalid names still raise (new test test_signature_functionlike_invalid_names).
Benchmark main PR Speedup
function, 7 params 22.95 µs 14.19 µs 1.62x
function, 1 param 8.18 µs 7.23 µs 1.13x
bound method 25.03 µs 21.03 µs 1.19x
671 stdlib functions 9.73 ms 7.36 ms 1.32x
Benchmark script

Windows x64 release build. Base and PR Lib/inspect.py were swapped via PYTHONPATH on the same interpreter. Each figure is the best of 3 interleaved runs, each the minimum of 15 repeats.

import inspect
import timeit


def f7(a, axis=None, dtype=None, out=None, keepdims=False, *, initial=None, where=True):
    pass


def f1(x):
    pass


class C:
    def method(self, a, b=1):
        pass


def stdlib_functions():
    import argparse, configparser, email.message, http.client, json, logging, subprocess
    funcs = []
    for mod in (argparse, configparser, email.message, http.client, json, logging, subprocess):
        for obj in vars(mod).values():
            if inspect.isfunction(obj):
                funcs.append(obj)
            elif inspect.isclass(obj):
                funcs.extend(v for v in vars(obj).values() if inspect.isfunction(v))
    return funcs


funcs = stdlib_functions()
bound = C().method
cases = {
    "function, 7 params": (lambda: inspect.signature(f7), 20_000),
    "function, 1 param": (lambda: inspect.signature(f1), 20_000),
    "bound method": (lambda: inspect.signature(bound), 20_000),
    f"{len(funcs)} stdlib functions": (lambda: [inspect.signature(f) for f in funcs], 50),
}
for name, (stmt, number) in cases.items():
    t = min(timeit.repeat(stmt, number=number, repeat=15)) / number
    unit, scale = ("ms", 1e3) if t > 1e-3 else ("us", 1e6)
    print(f"{name:28s} {t * scale:8.2f} {unit}")

Generated with Claude Code

eendebakpt and others added 2 commits September 13, 2026 21:36
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) <noreply@anthropic.com>
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 pythongh-150823), and that parameter names of
function-like objects are still validated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant