From 09ae3cbbb44833ccbca03ccab467c15bf5340098 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 15 Oct 2026 11:55:20 +0200 Subject: [PATCH 1/3] gh-87613: Use AC vectorcall for weakref.ref() Convert weakref.ref.__new__ to Argument Clinic with the @vectorcall decorator so that creating a weak reference no longer builds an argument tuple that is then unpacked twice (by __new__ and by __init__), and so that the call can be specialized as CALL_BUILTIN_CLASS. Co-Authored-By: Claude Fable 5.1 --- ...6-10-15-12-10-00.gh-issue-87613.Wr4fvc.rst | 1 + Objects/clinic/weakrefobject.c.h | 67 +++++++++++++++++++ Objects/weakrefobject.c | 37 ++++++---- 3 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-15-12-10-00.gh-issue-87613.Wr4fvc.rst create mode 100644 Objects/clinic/weakrefobject.c.h diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-15-12-10-00.gh-issue-87613.Wr4fvc.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-15-12-10-00.gh-issue-87613.Wr4fvc.rst new file mode 100644 index 000000000000000..8a57b1443365e7f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-15-12-10-00.gh-issue-87613.Wr4fvc.rst @@ -0,0 +1 @@ +Speed up creating :class:`weakref.ref` objects by adding a vectorcall implementation of the constructor generated by Argument Clinic. Patch by Pieter Eendebak. diff --git a/Objects/clinic/weakrefobject.c.h b/Objects/clinic/weakrefobject.c.h new file mode 100644 index 000000000000000..d2e10e40bb3bdfe --- /dev/null +++ b/Objects/clinic/weakrefobject.c.h @@ -0,0 +1,67 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +static PyObject * +weakref_ref_new_impl(PyTypeObject *type, PyObject *ob, PyObject *callback); + +static PyObject * +weakref_ref_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyTypeObject *base_tp = &_PyWeakref_RefType; + PyObject *ob; + PyObject *callback = NULL; + + if ((type == base_tp || type->tp_init == base_tp->tp_init) && + !_PyArg_NoKeywords("ref", kwargs)) { + goto exit; + } + if (!_PyArg_CheckPositional("ref", PyTuple_GET_SIZE(args), 1, 2)) { + goto exit; + } + ob = PyTuple_GET_ITEM(args, 0); + if (PyTuple_GET_SIZE(args) < 2) { + goto skip_optional; + } + callback = PyTuple_GET_ITEM(args, 1); +skip_optional: + return_value = weakref_ref_new_impl(type, ob, callback); + +exit: + return return_value; +} + +static PyObject * +weakref_ref_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *return_value = NULL; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *ob; + PyObject *callback = NULL; + + assert(Py_Is(_PyType_CAST(type), &_PyWeakref_RefType)); + /* Make sure the type object is immutable: the generated + * vectorcall doesn't deal e.g. with users reassigning __init__. */ + assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); + if (!_PyArg_NoKwnames("ref", kwnames)) { + goto exit; + } + if (!_PyArg_CheckPositional("ref", nargs, 1, 2)) { + goto exit; + } + ob = args[0]; + if (nargs < 2) { + goto skip_optional; + } + callback = args[1]; +skip_optional: + return_value = weakref_ref_new_impl(_PyType_CAST(type), ob, callback); + +exit: + return return_value; +} +/*[clinic end generated code: output=e376f863eef4ceb7 input=a9049054013a1b77]*/ diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index daeba5368b21660..d1bc1d54b8cc35b 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -7,6 +7,14 @@ #include "pycore_pystate.h" #include "pycore_weakref.h" // _PyWeakref_GET_REF() +#include "clinic/weakrefobject.c.h" + +/*[clinic input] +module weakref +class weakref.ref "PyWeakReference *" "&_PyWeakref_RefType" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ddbc2579691f1260]*/ + #ifdef Py_GIL_DISABLED /* * Thread-safety for free-threaded builds @@ -459,21 +467,21 @@ get_or_create_weakref(PyTypeObject *type, PyObject *obj, PyObject *callback) } } -static int -parse_weakref_init_args(const char *funcname, PyObject *args, PyObject *kwargs, - PyObject **obp, PyObject **callbackp) -{ - return PyArg_UnpackTuple(args, funcname, 1, 2, obp, callbackp); -} +/*[clinic input] +@classmethod +@vectorcall +weakref.ref.__new__ as weakref_ref_new + + ob: object + callback: object(c_default="NULL") = None + / +[clinic start generated code]*/ static PyObject * -weakref___new__(PyTypeObject *type, PyObject *args, PyObject *kwargs) +weakref_ref_new_impl(PyTypeObject *type, PyObject *ob, PyObject *callback) +/*[clinic end generated code: output=689332971ea485ce input=0b15234862919541]*/ { - PyObject *ob, *callback = NULL; - if (parse_weakref_init_args("__new__", args, kwargs, &ob, &callback)) { - return (PyObject *)get_or_create_weakref(type, ob, callback); - } - return NULL; + return (PyObject *)get_or_create_weakref(type, ob, callback); } static int @@ -484,7 +492,7 @@ weakref___init__(PyObject *self, PyObject *args, PyObject *kwargs) if (!_PyArg_NoKeywords("ref", kwargs)) return -1; - if (parse_weakref_init_args("__init__", args, kwargs, &tmp, &tmp)) + if (PyArg_UnpackTuple(args, "__init__", 1, 2, &tmp, &tmp)) return 0; else return -1; @@ -522,8 +530,9 @@ _PyWeakref_RefType = { .tp_members = weakref_members, .tp_init = weakref___init__, .tp_alloc = PyType_GenericAlloc, - .tp_new = weakref___new__, + .tp_new = weakref_ref_new, .tp_free = PyObject_GC_Del, + .tp_vectorcall = weakref_ref_vectorcall, }; From 08fbb8efd862bfb228eeea053b8becd5b699360c Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 15 Oct 2026 16:17:18 +0200 Subject: [PATCH 2/3] Convert weakref.ref.__init__ to Argument Clinic It only validates the arguments that __new__ already consumed, so the generated parser replaces the PyArg_UnpackTuple call and the arity error message now matches the one from __new__. Co-Authored-By: Claude Fable 5.1 --- Objects/clinic/weakrefobject.c.h | 34 +++++++++++++++++++++++++++++++- Objects/weakrefobject.c | 25 ++++++++++++----------- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Objects/clinic/weakrefobject.c.h b/Objects/clinic/weakrefobject.c.h index d2e10e40bb3bdfe..9efcc5cbf0a32ca 100644 --- a/Objects/clinic/weakrefobject.c.h +++ b/Objects/clinic/weakrefobject.c.h @@ -64,4 +64,36 @@ weakref_ref_vectorcall(PyObject *type, PyObject *const *args, exit: return return_value; } -/*[clinic end generated code: output=e376f863eef4ceb7 input=a9049054013a1b77]*/ + +static int +weakref_ref_init_impl(PyWeakReference *self, PyObject *ob, + PyObject *callback); + +static int +weakref_ref_init(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + PyTypeObject *base_tp = &_PyWeakref_RefType; + PyObject *ob; + PyObject *callback = Py_None; + + if ((Py_IS_TYPE(self, base_tp) || + Py_TYPE(self)->tp_new == base_tp->tp_new) && + !_PyArg_NoKeywords("ref", kwargs)) { + goto exit; + } + if (!_PyArg_CheckPositional("ref", PyTuple_GET_SIZE(args), 1, 2)) { + goto exit; + } + ob = PyTuple_GET_ITEM(args, 0); + if (PyTuple_GET_SIZE(args) < 2) { + goto skip_optional; + } + callback = PyTuple_GET_ITEM(args, 1); +skip_optional: + return_value = weakref_ref_init_impl((PyWeakReference *)self, ob, callback); + +exit: + return return_value; +} +/*[clinic end generated code: output=d3dd6d5b2987762f input=a9049054013a1b77]*/ diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index d1bc1d54b8cc35b..ff85f3a7d176d09 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -484,18 +484,21 @@ weakref_ref_new_impl(PyTypeObject *type, PyObject *ob, PyObject *callback) return (PyObject *)get_or_create_weakref(type, ob, callback); } -static int -weakref___init__(PyObject *self, PyObject *args, PyObject *kwargs) -{ - PyObject *tmp; +/*[clinic input] +weakref.ref.__init__ as weakref_ref_init - if (!_PyArg_NoKeywords("ref", kwargs)) - return -1; + ob: object + callback: object = None + / +[clinic start generated code]*/ - if (PyArg_UnpackTuple(args, "__init__", 1, 2, &tmp, &tmp)) - return 0; - else - return -1; +static int +weakref_ref_init_impl(PyWeakReference *self, PyObject *ob, + PyObject *callback) +/*[clinic end generated code: output=57cec8c3671e296c input=d9ac4909b0fc36bf]*/ +{ + /* The arguments were already used by __new__; only validate them. */ + return 0; } @@ -528,7 +531,7 @@ _PyWeakref_RefType = { .tp_richcompare = weakref_richcompare, .tp_methods = weakref_methods, .tp_members = weakref_members, - .tp_init = weakref___init__, + .tp_init = weakref_ref_init, .tp_alloc = PyType_GenericAlloc, .tp_new = weakref_ref_new, .tp_free = PyObject_GC_Del, From 01367935221ac0be098378ee8549c6b5762870da Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 15 Oct 2026 16:47:33 +0200 Subject: [PATCH 3/3] Give weakref.ref a docstring and text signature Name the Argument Clinic class after tp_name (weakref.ReferenceType) so that the generated docstring's signature line is recognised, which makes help(weakref.ref) show the signature and inspect.signature() work. The argument errors now name ReferenceType instead of ref/__new__/__init__. Co-Authored-By: Claude Fable 5.1 --- Lib/test/test_inspect/test_inspect.py | 3 +-- Objects/clinic/weakrefobject.c.h | 24 +++++++++++++++++------- Objects/weakrefobject.c | 19 +++++++++++++------ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 3f14a9e66226eaa..d86d2a8722ae0e3 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6463,8 +6463,7 @@ def test_warnings_module_has_signatures(self): def test_weakref_module_has_signatures(self): import weakref - no_signature = {'ReferenceType', 'ref'} - self._test_module_has_signatures(weakref, no_signature) + self._test_module_has_signatures(weakref) def test_python_function_override_signature(self): def func(*args, **kwargs): diff --git a/Objects/clinic/weakrefobject.c.h b/Objects/clinic/weakrefobject.c.h index 9efcc5cbf0a32ca..654c79dad256de6 100644 --- a/Objects/clinic/weakrefobject.c.h +++ b/Objects/clinic/weakrefobject.c.h @@ -4,6 +4,16 @@ preserve #include "pycore_modsupport.h" // _PyArg_CheckPositional() +PyDoc_STRVAR(weakref_ref_new__doc__, +"ReferenceType(ob, callback=None, /)\n" +"--\n" +"\n" +"Create a weak reference to ob.\n" +"\n" +"Calling the reference returns ob, or None once ob has been garbage\n" +"collected. If callback is given, it is called with the reference as\n" +"its only argument when ob is about to be finalized."); + static PyObject * weakref_ref_new_impl(PyTypeObject *type, PyObject *ob, PyObject *callback); @@ -16,10 +26,10 @@ weakref_ref_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *callback = NULL; if ((type == base_tp || type->tp_init == base_tp->tp_init) && - !_PyArg_NoKeywords("ref", kwargs)) { + !_PyArg_NoKeywords("ReferenceType", kwargs)) { goto exit; } - if (!_PyArg_CheckPositional("ref", PyTuple_GET_SIZE(args), 1, 2)) { + if (!_PyArg_CheckPositional("ReferenceType", PyTuple_GET_SIZE(args), 1, 2)) { goto exit; } ob = PyTuple_GET_ITEM(args, 0); @@ -47,10 +57,10 @@ weakref_ref_vectorcall(PyObject *type, PyObject *const *args, /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); - if (!_PyArg_NoKwnames("ref", kwnames)) { + if (!_PyArg_NoKwnames("ReferenceType", kwnames)) { goto exit; } - if (!_PyArg_CheckPositional("ref", nargs, 1, 2)) { + if (!_PyArg_CheckPositional("ReferenceType", nargs, 1, 2)) { goto exit; } ob = args[0]; @@ -79,10 +89,10 @@ weakref_ref_init(PyObject *self, PyObject *args, PyObject *kwargs) if ((Py_IS_TYPE(self, base_tp) || Py_TYPE(self)->tp_new == base_tp->tp_new) && - !_PyArg_NoKeywords("ref", kwargs)) { + !_PyArg_NoKeywords("ReferenceType", kwargs)) { goto exit; } - if (!_PyArg_CheckPositional("ref", PyTuple_GET_SIZE(args), 1, 2)) { + if (!_PyArg_CheckPositional("ReferenceType", PyTuple_GET_SIZE(args), 1, 2)) { goto exit; } ob = PyTuple_GET_ITEM(args, 0); @@ -96,4 +106,4 @@ weakref_ref_init(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=d3dd6d5b2987762f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e49af92091232111 input=a9049054013a1b77]*/ diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index ff85f3a7d176d09..1635632ca713366 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -11,9 +11,9 @@ /*[clinic input] module weakref -class weakref.ref "PyWeakReference *" "&_PyWeakref_RefType" +class weakref.ReferenceType "PyWeakReference *" "&_PyWeakref_RefType" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ddbc2579691f1260]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=25211e4a7d687f3f]*/ #ifdef Py_GIL_DISABLED /* @@ -470,22 +470,28 @@ get_or_create_weakref(PyTypeObject *type, PyObject *obj, PyObject *callback) /*[clinic input] @classmethod @vectorcall -weakref.ref.__new__ as weakref_ref_new +weakref.ReferenceType.__new__ as weakref_ref_new ob: object callback: object(c_default="NULL") = None / + +Create a weak reference to ob. + +Calling the reference returns ob, or None once ob has been garbage +collected. If callback is given, it is called with the reference as +its only argument when ob is about to be finalized. [clinic start generated code]*/ static PyObject * weakref_ref_new_impl(PyTypeObject *type, PyObject *ob, PyObject *callback) -/*[clinic end generated code: output=689332971ea485ce input=0b15234862919541]*/ +/*[clinic end generated code: output=689332971ea485ce input=f9264872536c6be7]*/ { return (PyObject *)get_or_create_weakref(type, ob, callback); } /*[clinic input] -weakref.ref.__init__ as weakref_ref_init +weakref.ReferenceType.__init__ as weakref_ref_init ob: object callback: object = None @@ -495,7 +501,7 @@ weakref.ref.__init__ as weakref_ref_init static int weakref_ref_init_impl(PyWeakReference *self, PyObject *ob, PyObject *callback) -/*[clinic end generated code: output=57cec8c3671e296c input=d9ac4909b0fc36bf]*/ +/*[clinic end generated code: output=57cec8c3671e296c input=7dd1cba6e88c7df8]*/ { /* The arguments were already used by __new__; only validate them. */ return 0; @@ -519,6 +525,7 @@ _PyWeakref_RefType = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "weakref.ReferenceType", .tp_basicsize = sizeof(PyWeakReference), + .tp_doc = weakref_ref_new__doc__, .tp_dealloc = weakref_dealloc, .tp_vectorcall_offset = offsetof(PyWeakReference, vectorcall), .tp_call = PyVectorcall_Call,