Skip to content

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

Open
gaborbernat wants to merge 1 commit into
python:mainfrom
gaborbernat:opt/inspect-signature-fast-param
Open

gh-150816: Speed up inspect.signature() for Python functions#150823
gaborbernat wants to merge 1 commit into
python:mainfrom
gaborbernat:opt/inspect-signature-fast-param

Conversation

@gaborbernat
Copy link
Copy Markdown
Contributor

@gaborbernat gaborbernat commented Jun 2, 2026

inspect.signature() builds a Signature whose every Parameter goes through the public Parameter constructor, which validates the name and the kind on each call. When the parameters come from a function's own code object, the names are already valid identifiers and the kinds are already the canonical constants, so that validation re-checks facts that are guaranteed to hold. Signature introspection runs constantly across the ecosystem — web frameworks resolving view and dependency arguments, click building commands, pytest wiring fixtures, serialization and validation layers — usually once per function and often at import time while an application is assembled.

This adds an internal Parameter._create() used only by _signature_from_function, the trusted caller that reads parameters from a code object. It populates the instance directly and skips the redundant checks. The one case that still needs the constructor's handling, a comprehension's implicit .0 argument, is detected and deferred to it. The public Parameter() constructor and all of its validation are untouched, so every other caller behaves exactly as before.

Taking the signature of 400 real callables collected from popular packages (requests, click, jinja2, sqlalchemy, werkzeug, flask and others) improves from 1.46 ms to 1.16 ms, 26% faster.

Benchmark base patched
inspect.signature over 400 real callables 1.46 ms 1.16 ms: 26% faster
Benchmark (pyperf)

Run base vs patched by swapping Lib/inspect.py on the same interpreter. The script collects callables from installed third-party packages when present and always includes several stdlib modules, so it runs with no extra installs.

import inspect, importlib, pyperf

names = ["requests", "click", "jinja2", "sqlalchemy", "werkzeug", "flask",
         "urllib3", "rich.console", "json", "argparse", "logging", "http.client",
         "email.message", "configparser", "xml.etree.ElementTree", "subprocess"]
funcs = []
for name in names:
    try:
        mod = importlib.import_module(name)
    except ImportError:
        continue
    for attr in dir(mod):
        obj = getattr(mod, attr, None)
        if inspect.isfunction(obj):
            funcs.append(obj)
        elif inspect.isclass(obj):
            funcs.extend(m for m in vars(obj).values() if inspect.isfunction(m))
good = []
for f in funcs:
    try:
        inspect.signature(f); good.append(f)
    except (ValueError, TypeError):
        pass
good = good[:400]

runner = pyperf.Runner()
runner.metadata["n_callables"] = len(good)
runner.bench_func("inspect.signature over %d callables" % len(good),
                  lambda: [inspect.signature(f) for f in good])

Resolves #150816.

Parameters built from a function's code object always have valid identifier
names and canonical kinds, yet each one is constructed through the validating
Parameter() constructor. Add Parameter._create() for these trusted callers to
skip the redundant checks; comprehension implicit args still defer to __init__.
The public constructor and its validation are unchanged.
@gaborbernat gaborbernat force-pushed the opt/inspect-signature-fast-param branch from 12b3b99 to fafbd85 Compare June 2, 2026 23:45
@skirpichev skirpichev self-requested a review June 3, 2026 03:53
@skirpichev
Copy link
Copy Markdown
Member

In order to keep the commit history intact, please avoid squashing or amending history and then force-pushing to the PR. Reviewers often want to look at individual commits. When the PR is merged, everything will be squashed into a single commit.

Copy link
Copy Markdown
Member

@skirpichev skirpichev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, with few comments.

CC @sobolevn

Comment thread Lib/inspect.py
self._name = name

@classmethod
def _create(cls, name, kind, default, annotation):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _create(cls, name, kind, default, annotation):
def _from_valid_args(cls, name, kind, default, annotation):

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bikeshed is painted _unchecked_create.

Comment thread Lib/inspect.py
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.

Speed up inspect.signature() for Python functions

4 participants