Repository navigation
Deliver defaulted positional-only hook arguments positionally - #775
adenzhou1350 wants to merge 2 commits into
Conversation
Fixes pytest-dev#774 Assisted-by: OpenAI Codex Signed-off-by: Xucheng Zhou <aden1350@outlook.com>
Assisted-by: OpenAI Codex Signed-off-by: Xucheng Zhou <aden1350@outlook.com>
RonnyPfannschmidt
left a comment
There was a problem hiding this comment.
🤖 Written by Claude Opus 5.5 via Claude Code for the pluggy maintainers; I prompted it, it did the work, I read it.
Approving. I measured the runtime cost against main before approving. The setup was CPython 3.13, a micro-benchmark, 2 rounds, and the best of 15 runs for each case:
| Case | main | this PR | Δ |
|---|---|---|---|
| Hook call, 10 impls, no defaulted args | ~4.3 µs | ~4.35 µs | noise |
Hook call, 10 impls with b=None, c=None |
~7.2 µs | ~7.9 µs | +8–12% (~60–80 ns per impl) |
| Hook call, 10 impls with positional-only defaults | TypeError | ~15.2 µs | new path, ~1.2 µs per impl |
HookImpl() construction |
~2.55 µs | ~4.35 µs | +70% (~1.8 µs per impl) |
- Impls without defaulted args are unaffected, because all the new code is inside
if hook_impl.kwargnames:. - Impls with defaulted args now pay on every call for the
_posonlydefaultstruthiness check. They also pay for anot in _posonlydefaultslookup for each kwarg inside the kwargs comprehension. - The registration cost is one-time and negligible in absolute terms.
Follow-up (not blocking, will be folded into a refactoring PR stack): move the split to HookImpl.__init__. That means precomputing a kwargnames tuple without the positional-only names for the kwargs comprehension, plus a precomputed (name, default) sequence for the positional-only fill. The call path then only touches the positional-only branch when it is non-empty. With that, impls with ordinary defaults should cost the same as on main, and only positional-only impls pay extra.
Generated by Claude Code
Fixes #774.
Hookimpl arguments with defaults are forwarded as keywords, including positional-only arguments. An explicit call value therefore raises
TypeErrorin an otherwise valid hook or wrapper.Remember optional positional-only names/defaults during registration and deliver them positionally. Fill gaps before a supplied positional-only value, but leave trailing omitted defaults implicit. Keep required-argument validation and hookspec/implementation default precedence unchanged; no keyword-only support change.
Regression coverage includes plain hooks, both wrapper styles, bound methods, callable/decorated
call_extraimplementations, falsy values, mixed signatures and default gaps.Validation:
Environment note: the committed uv lock selects pytest 3.2.5, which cannot start on Python 3.12. Linux tests used a task-private editable install and pytest 9.1.1 via
uv run --no-sync --no-default-groups python -m pytest; Windows used the native interpreter and isolated package targets because local venv executables are blocked. No supported-Python matrix or performance claim.AI-assisted with OpenAI Codex; the implementation and tests were independently checked against the unmodified source.
Coverage follow-up: add native class-constructor call_extra cases and express the required default prefix without an unreachable loop-exhaustion branch. Both changed production modules now have 100% statement/branch coverage locally; no coverage threshold or exclusion changed. Upstream matrix/package/benchmark checks passed on the initial revision; reruns for the updated head are pending.