Skip to content

gh-157407: Argument Clinic: parse positional-only defining_class methods without _PyArg_Parser - #157483

Open
eendebakpt wants to merge 1 commit into
python:mainfrom
eendebakpt:feature/clinic-posonly-defclass-2
Open

eendebakpt wants to merge 1 commit into
python:mainfrom
eendebakpt:feature/clinic-posonly-defclass-2

Conversation

@eendebakpt

@eendebakpt eendebakpt commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Methods taking a defining_class whose other parameters are all positional-only (e.g. Decimal.from_float) used the keyword path: a static _PyArg_Parser and a _PyArg_UnpackKeywords() call. They now use _PyArg_NoKwnames().

Typical code change:

-    static const char * const _keywords[] = {"", NULL};
-    static _PyArg_Parser _parser = {
-        .keywords = _keywords,
-        .fname = "from_float",
-        .kwtuple = KWTUPLE,
-    };
-    PyObject *argsbuf[1];
     PyObject *pyfloat;
 
-    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
-            /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
-    if (!args) {
+    if (!_PyArg_NoKwnames("from_float", kwnames)) {
+        goto exit;
+    }
+    if (!_PyArg_CheckPositional("from_float", nargs, 1, 1)) {

This converts 135 methods in 19 clinic headers. _PyArg_NoKwnames() is now exported for shared extension modules.

Size change in bytes:

.text read-only data .data
Windows x64 (MSVC, LTCG) −6352 −4708 −10512
Linux x86-64 (gcc -O3)* −2592 −1144 −5664

* excluding _decimal, _sqlite3, _dbm and _gdbm, which were not built. Linux also drops 4872 bytes of .rela.dyn.

Changed error messages: these methods now raise the same TypeError messages as other positional-only methods,:

call before after
compressobj().compress() compress() takes exactly 1 positional argument (0 given) compress expected 1 argument, got 0
compressobj().compress(b'', b'') compress() takes at most 1 argument (2 given) compress expected 1 argument, got 2
Pickler(f).dump(obj=1) dump() takes exactly 1 positional argument (0 given) dump() takes no keyword arguments

This is arguably more consistent, but a small change in error messages.

Performance seems slightly improved (but within noise, no not quoting any numbers).

Generated with Claude Code

…s methods

A method that takes a defining class and whose other parameters are all
positional-only (e.g. decimal.Decimal.from_float) was routed through
Argument Clinic's generic keyword path: it emitted a static _PyArg_Parser,
an (empty) keyword tuple and an _PyArg_UnpackKeywords() call, even though
it accepts no keyword arguments.

Such methods now go through parse_pos_only(): the wrapper still uses the
mandatory METH_METHOD|METH_FASTCALL|METH_KEYWORDS convention (the only one
that delivers the defining class), but it rejects keywords with
_PyArg_NoKwnames() and reads positional arguments directly, with no
parser, keyword tuple or argsbuf.

This converts 135 methods across 19 clinic headers, removing the static
_PyArg_Parser, the keyword arrays/tuples and the per-call
_PyArg_UnpackKeywords() + parser_init() overhead for each.

_PyArg_NoKwnames() is now exported (PyAPI_FUNC) so generated code in
shared extension modules can use it, like _PyArg_NoKeywords().

Error messages for these methods now match ordinary positional-only
functions ("expected ... arguments" / "takes no keyword arguments"
instead of the _PyArg_UnpackKeywords phrasing); affected stdlib tests
are updated accordingly.

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.

2 participants