diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 7115359156eb696..8d8b7f9bbc38fb3 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -208,7 +208,7 @@ def test_varargs14_kw(self): itertools.product, 0, repeat=1, foo=2) def test_varargs15_kw(self): - msg = r"^ImportError\(\) takes at most 3 keyword arguments \(4 given\)$" + msg = r"^ImportError\(\) got an unexpected keyword argument 'foo'$" self.assertRaisesRegex(TypeError, msg, ImportError, 0, name=1, path=2, name_from=3, foo=3) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-21-21-04.gh-issue-161242.d4vNgC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-21-21-04.gh-issue-161242.d4vNgC.rst new file mode 100644 index 000000000000000..3dbd521612feedb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-21-21-04.gh-issue-161242.d4vNgC.rst @@ -0,0 +1,3 @@ +Speed up :exc:`AttributeError`, :exc:`ImportError` and +:exc:`NameError` by enabling :ref:`vectorcall`. Unexpected +keyword arguments are now reported by name. diff --git a/Objects/clinic/exceptions.c.h b/Objects/clinic/exceptions.c.h index c06cdb0444ee971..b77606c3c7a8c76 100644 --- a/Objects/clinic/exceptions.c.h +++ b/Objects/clinic/exceptions.c.h @@ -2,8 +2,12 @@ preserve [clinic start generated code]*/ +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +#endif #include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_BadArgument() +#include "pycore_runtime.h" // _Py_SINGLETON() static PyObject * BaseException_str_impl(PyBaseExceptionObject *self); @@ -362,6 +366,329 @@ BaseExceptionGroup_subgroup(PyObject *self, PyObject *matcher_value) return return_value; } + +static int +ImportError_init_impl(PyImportErrorObject *self, PyObject *args, + PyObject *name, PyObject *path, PyObject *name_from); + +static int +ImportError_init_helper(PyObject *self, PyObject *const *args, + Py_ssize_t nargs, Py_ssize_t nkw, PyObject *kwargs, PyObject *kwnames) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(name), &_Py_ID(path), &_Py_ID(name_from), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"name", "path", "name_from", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "ImportError", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + PyObject * const *fastargs; + Py_ssize_t noptargs = 0 + nkw - 0; + PyObject *__clinic_args = NULL; + PyObject *name = NULL; + PyObject *path = NULL; + PyObject *name_from = NULL; + + fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser, + /*minpos*/ 0, /*maxpos*/ 0, /*minkw*/ 0, /*varpos*/ 1, argsbuf); + if (!fastargs) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + if (fastargs[0]) { + name = fastargs[0]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (fastargs[1]) { + path = fastargs[1]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + name_from = fastargs[2]; +skip_optional_kwonly: + __clinic_args = PyTuple_FromArray(args, nargs); + if (__clinic_args == NULL) { + goto exit; + } + return_value = ImportError_init_impl((PyImportErrorObject *)self, __clinic_args, name, path, name_from); + +exit: + /* Cleanup for args */ + Py_XDECREF(__clinic_args); + + return return_value; +} + +static int +ImportError_init(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return ImportError_init_helper(self, _PyTuple_CAST(args)->ob_item, + PyTuple_GET_SIZE(args), + kwargs ? PyDict_GET_SIZE(kwargs) : 0, + kwargs, NULL); +} + +static PyObject * +ImportError_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *self; + int _result; + + assert(_PyType_CAST(type)->tp_init == ImportError_init); + /* 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)); + self = _PyType_CAST(type)->tp_new(_PyType_CAST(type), + (PyObject *)&_Py_SINGLETON(tuple_empty), NULL); + if (self == NULL) { + return NULL; + } + _result = ImportError_init_helper(self, args, nargs, + kwnames ? PyTuple_GET_SIZE(kwnames) : 0, + NULL, kwnames); + if (_result != 0) { + Py_DECREF(self); + return NULL; + } + return self; +} + +static int +NameError_init_impl(PyNameErrorObject *self, PyObject *args, PyObject *name); + +static int +NameError_init_helper(PyObject *self, PyObject *const *args, + Py_ssize_t nargs, Py_ssize_t nkw, PyObject *kwargs, PyObject *kwnames) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"name", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "NameError", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject * const *fastargs; + Py_ssize_t noptargs = 0 + nkw - 0; + PyObject *__clinic_args = NULL; + PyObject *name = NULL; + + fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser, + /*minpos*/ 0, /*maxpos*/ 0, /*minkw*/ 0, /*varpos*/ 1, argsbuf); + if (!fastargs) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + name = fastargs[0]; +skip_optional_kwonly: + __clinic_args = PyTuple_FromArray(args, nargs); + if (__clinic_args == NULL) { + goto exit; + } + return_value = NameError_init_impl((PyNameErrorObject *)self, __clinic_args, name); + +exit: + /* Cleanup for args */ + Py_XDECREF(__clinic_args); + + return return_value; +} + +static int +NameError_init(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return NameError_init_helper(self, _PyTuple_CAST(args)->ob_item, + PyTuple_GET_SIZE(args), + kwargs ? PyDict_GET_SIZE(kwargs) : 0, + kwargs, NULL); +} + +static PyObject * +NameError_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *self; + int _result; + + assert(_PyType_CAST(type)->tp_init == NameError_init); + /* 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)); + self = _PyType_CAST(type)->tp_new(_PyType_CAST(type), + (PyObject *)&_Py_SINGLETON(tuple_empty), NULL); + if (self == NULL) { + return NULL; + } + _result = NameError_init_helper(self, args, nargs, + kwnames ? PyTuple_GET_SIZE(kwnames) : 0, + NULL, kwnames); + if (_result != 0) { + Py_DECREF(self); + return NULL; + } + return self; +} + +static int +AttributeError_init_impl(PyAttributeErrorObject *self, PyObject *args, + PyObject *name, PyObject *obj); + +static int +AttributeError_init_helper(PyObject *self, PyObject *const *args, + Py_ssize_t nargs, Py_ssize_t nkw, PyObject *kwargs, PyObject *kwnames) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(name), &_Py_ID(obj), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"name", "obj", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "AttributeError", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject * const *fastargs; + Py_ssize_t noptargs = 0 + nkw - 0; + PyObject *__clinic_args = NULL; + PyObject *name = NULL; + PyObject *obj = NULL; + + fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser, + /*minpos*/ 0, /*maxpos*/ 0, /*minkw*/ 0, /*varpos*/ 1, argsbuf); + if (!fastargs) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + if (fastargs[0]) { + name = fastargs[0]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + obj = fastargs[1]; +skip_optional_kwonly: + __clinic_args = PyTuple_FromArray(args, nargs); + if (__clinic_args == NULL) { + goto exit; + } + return_value = AttributeError_init_impl((PyAttributeErrorObject *)self, __clinic_args, name, obj); + +exit: + /* Cleanup for args */ + Py_XDECREF(__clinic_args); + + return return_value; +} + +static int +AttributeError_init(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return AttributeError_init_helper(self, _PyTuple_CAST(args)->ob_item, + PyTuple_GET_SIZE(args), + kwargs ? PyDict_GET_SIZE(kwargs) : 0, + kwargs, NULL); +} + +static PyObject * +AttributeError_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *self; + int _result; + + assert(_PyType_CAST(type)->tp_init == AttributeError_init); + /* 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)); + self = _PyType_CAST(type)->tp_new(_PyType_CAST(type), + (PyObject *)&_Py_SINGLETON(tuple_empty), NULL); + if (self == NULL) { + return NULL; + } + _result = AttributeError_init_helper(self, args, nargs, + kwnames ? PyTuple_GET_SIZE(kwnames) : 0, + NULL, kwnames); + if (_result != 0) { + Py_DECREF(self); + return NULL; + } + return self; +} #define BASEEXCEPTION_ARGS_GETSETDEF {"args", (getter)BaseException_args_get, (setter)BaseException_args_set, NULL}, #define BASEEXCEPTION___TRACEBACK___GETSETDEF {"__traceback__", (getter)BaseException___traceback___get, (setter)BaseException___traceback___set, NULL}, @@ -370,4 +697,4 @@ BaseExceptionGroup_subgroup(PyObject *self, PyObject *matcher_value) #define BASEEXCEPTION___CAUSE___GETSETDEF {"__cause__", (getter)BaseException___cause___get, (setter)BaseException___cause___set, NULL}, -/*[clinic end generated code: output=20829af0c376b1e9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c8eaffee13bc041d input=a9049054013a1b77]*/ diff --git a/Objects/exceptions.c b/Objects/exceptions.c index e2f757c5ee9c0be..dc0ba0d7e533dda 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -24,8 +24,11 @@ /*[clinic input] class BaseException "PyBaseExceptionObject *" "&PyExc_BaseException" class BaseExceptionGroup "PyBaseExceptionGroupObject *" "&PyExc_BaseExceptionGroup" +class ImportError "PyImportErrorObject *" "&PyExc_ImportError" +class NameError "PyNameErrorObject *" "&PyExc_NameError" +class AttributeError "PyAttributeErrorObject *" "&PyExc_AttributeError" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b7c45e78cff8edc3]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=fce4682940ab8948]*/ static struct _Py_exc_state* @@ -1778,29 +1781,27 @@ PyImportErrorObject_CAST(PyObject *self) return (PyImportErrorObject *)self; } +/*[clinic input] +@vectorcall +ImportError.__init__ as ImportError_init + + *args: tuple + name: object = NULL + path: object = NULL + name_from: object = NULL + +[clinic start generated code]*/ + static int -ImportError_init(PyObject *op, PyObject *args, PyObject *kwds) +ImportError_init_impl(PyImportErrorObject *self, PyObject *args, + PyObject *name, PyObject *path, PyObject *name_from) +/*[clinic end generated code: output=4917add0f9473bd2 input=4e94a3b530f9be97]*/ { - static char *kwlist[] = {"name", "path", "name_from", 0}; - PyObject *empty_tuple; PyObject *msg = NULL; - PyObject *name = NULL; - PyObject *path = NULL; - PyObject *name_from = NULL; - if (BaseException_init(op, args, NULL) == -1) - return -1; - - PyImportErrorObject *self = PyImportErrorObject_CAST(op); - empty_tuple = PyTuple_New(0); - if (!empty_tuple) - return -1; - if (!PyArg_ParseTupleAndKeywords(empty_tuple, kwds, "|$OOO:ImportError", kwlist, - &name, &path, &name_from)) { - Py_DECREF(empty_tuple); + if (BaseException_init((PyObject *)self, args, NULL) == -1) { return -1; } - Py_DECREF(empty_tuple); Py_XSETREF(self->name, Py_XNewRef(name)); Py_XSETREF(self->path, Py_XNewRef(path)); @@ -1814,6 +1815,7 @@ ImportError_init(PyObject *op, PyObject *args, PyObject *kwds) return 0; } + static int ImportError_clear(PyObject *op) { @@ -2579,33 +2581,29 @@ PyNameErrorObject_CAST(PyObject *self) return (PyNameErrorObject *)self; } -static int -NameError_init(PyObject *op, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"name", NULL}; - PyObject *name = NULL; +/*[clinic input] +@vectorcall +NameError.__init__ as NameError_init - if (BaseException_init(op, args, NULL) == -1) { - return -1; - } + *args: tuple + name: object = NULL - PyObject *empty_tuple = PyTuple_New(0); - if (!empty_tuple) { - return -1; - } - if (!PyArg_ParseTupleAndKeywords(empty_tuple, kwds, "|$O:NameError", kwlist, - &name)) { - Py_DECREF(empty_tuple); +[clinic start generated code]*/ + +static int +NameError_init_impl(PyNameErrorObject *self, PyObject *args, PyObject *name) +/*[clinic end generated code: output=f15305075d9aa272 input=2e2b7e781ae146b1]*/ +{ + if (BaseException_init((PyObject *)self, args, NULL) == -1) { return -1; } - Py_DECREF(empty_tuple); - PyNameErrorObject *self = PyNameErrorObject_CAST(op); Py_XSETREF(self->name, Py_XNewRef(name)); return 0; } + static int NameError_clear(PyObject *op) { @@ -2662,35 +2660,32 @@ PyAttributeErrorObject_CAST(PyObject *self) return (PyAttributeErrorObject *)self; } -static int -AttributeError_init(PyObject *op, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"name", "obj", NULL}; - PyObject *name = NULL; - PyObject *obj = NULL; +/*[clinic input] +@vectorcall +AttributeError.__init__ as AttributeError_init - if (BaseException_init(op, args, NULL) == -1) { - return -1; - } + *args: tuple + name: object = NULL + obj: object = NULL - PyObject *empty_tuple = PyTuple_New(0); - if (!empty_tuple) { - return -1; - } - if (!PyArg_ParseTupleAndKeywords(empty_tuple, kwds, "|$OO:AttributeError", kwlist, - &name, &obj)) { - Py_DECREF(empty_tuple); +[clinic start generated code]*/ + +static int +AttributeError_init_impl(PyAttributeErrorObject *self, PyObject *args, + PyObject *name, PyObject *obj) +/*[clinic end generated code: output=3d346f9a389f60fa input=bed43aafea47f1a0]*/ +{ + if (BaseException_init((PyObject *)self, args, NULL) == -1) { return -1; } - Py_DECREF(empty_tuple); - PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op); Py_XSETREF(self->name, Py_XNewRef(name)); Py_XSETREF(self->obj, Py_XNewRef(obj)); return 0; } + static int AttributeError_clear(PyObject *op) { @@ -4565,6 +4560,15 @@ _PyExc_InitTypes(PyInterpreterState *interp) { exc->tp_vectorcall = BaseException_vectorcall; } + else if (exc->tp_init == ImportError_init) { + exc->tp_vectorcall = ImportError_vectorcall; + } + else if (exc->tp_init == NameError_init) { + exc->tp_vectorcall = NameError_vectorcall; + } + else if (exc->tp_init == AttributeError_init) { + exc->tp_vectorcall = AttributeError_vectorcall; + } } return 0; }