Skip to content

Commit d2775b4

Browse files
RonnyPfannschmidtCursor AIclaude
committed
Add varnames benchmark comparing __code__ vs inspect.signature
Include both the current implementation and the legacy inspect.signature-based version to clearly demonstrate the ~7-66x speedup from using code objects directly. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
1 parent 4fa3df1 commit d2775b4

1 file changed

Lines changed: 90 additions & 0 deletions

File tree

testing/benchmark.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Benchmarking and performance tests.
33
"""
44

5+
import inspect
6+
import sys
57
from typing import Any
68

79
import pytest
@@ -11,6 +13,67 @@
1113
from pluggy import PluginManager
1214
from pluggy._callers import _multicall
1315
from pluggy._hooks import HookImpl
16+
from pluggy._hooks import varnames
17+
18+
19+
_PYPY = hasattr(sys, "pypy_version_info")
20+
21+
22+
def _varnames_legacy(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]:
23+
"""Pre-PEP 649 implementation using inspect.signature for comparison."""
24+
if inspect.isclass(func):
25+
try:
26+
func = func.__init__
27+
except AttributeError:
28+
return (), ()
29+
elif not inspect.isroutine(func):
30+
try:
31+
func = getattr(func, "__call__", func)
32+
except Exception:
33+
return (), ()
34+
35+
try:
36+
sig = inspect.signature(
37+
func.__func__ if inspect.ismethod(func) else func # type: ignore[arg-type]
38+
)
39+
except TypeError:
40+
return (), ()
41+
42+
_valid_param_kinds = (
43+
inspect.Parameter.POSITIONAL_ONLY,
44+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
45+
)
46+
_valid_params = {
47+
name: param
48+
for name, param in sig.parameters.items()
49+
if param.kind in _valid_param_kinds
50+
}
51+
args = tuple(_valid_params)
52+
defaults = (
53+
tuple(
54+
param.default
55+
for param in _valid_params.values()
56+
if param.default is not param.empty
57+
)
58+
or None
59+
)
60+
61+
if defaults:
62+
index = -len(defaults)
63+
args, kwargs = args[:index], tuple(args[index:])
64+
else:
65+
kwargs = ()
66+
67+
if not _PYPY:
68+
implicit_names: tuple[str, ...] = ("self",)
69+
else:
70+
implicit_names = ("self", "obj")
71+
if args:
72+
qualname: str = getattr(func, "__qualname__", "")
73+
if inspect.ismethod(func) or ("." in qualname and args[0] in implicit_names):
74+
args = args[1:]
75+
76+
return args, kwargs
1477

1578

1679
hookspec = HookspecMarker("example")
@@ -106,3 +169,30 @@ def fun(self):
106169
pm.register(PluginWrap(i), name=f"wrap_plug_{i}")
107170

108171
benchmark(pm.hook.fun, hooks=pm.hook, nesting=nesting)
172+
173+
174+
def _plain_func(x: int, y: str, z: float = 1.0) -> None:
175+
pass
176+
177+
178+
class _MethodHolder:
179+
def method(self, x: int, y: str, z: float = 1.0) -> None:
180+
pass
181+
182+
183+
_varnames_funcs = [
184+
pytest.param(_plain_func, id="plain_function"),
185+
pytest.param(_MethodHolder.method, id="unbound_method"),
186+
pytest.param(_MethodHolder().method, id="bound_method"),
187+
pytest.param(_MethodHolder, id="class"),
188+
]
189+
190+
191+
@pytest.mark.parametrize("func", _varnames_funcs)
192+
def test_varnames(benchmark, func: object) -> None:
193+
benchmark(varnames, func)
194+
195+
196+
@pytest.mark.parametrize("func", _varnames_funcs)
197+
def test_varnames_legacy(benchmark, func: object) -> None:
198+
benchmark(_varnames_legacy, func)

0 commit comments

Comments
 (0)