diff --git a/Misc/NEWS.d/next/Library/2026-09-25-01-29-55.gh-issue-158126.nWmFnq.rst b/Misc/NEWS.d/next/Library/2026-09-25-01-29-55.gh-issue-158126.nWmFnq.rst new file mode 100644 index 000000000000000..82bf49faef56d4b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-25-01-29-55.gh-issue-158126.nWmFnq.rst @@ -0,0 +1,3 @@ +Speed up :func:`pickle.dumps` and creating a :class:`pickle.Pickler` by +avoiding the creation of an :exc:`AttributeError` when looking up the +optional ``reducer_override`` and ``dispatch_table`` attributes. diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 5339337741d2f27..ab9ab46ae99f45a 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -4839,6 +4839,25 @@ persistent_id(PyObject *self, PyObject *obj) Py_RETURN_NONE; } +static PyObject *Pickler_getattr(PyObject *self, PyObject *name); + +/* Like PyObject_GetOptionalAttr(), but avoids creating an AttributeError + for a missing attribute. Must not be used for "persistent_id". */ +static int +pickler_get_optional_attr(PicklerObject *self, PyObject *name, + PyObject **result) +{ + if (Py_TYPE(self)->tp_getattro == Pickler_getattr) { + *result = _PyObject_GenericGetAttrWithDict((PyObject *)self, name, + NULL, 1); + if (*result != NULL) { + return 1; + } + return PyErr_Occurred() ? -1 : 0; + } + return PyObject_GetOptionalAttr((PyObject *)self, name, result); +} + static int dump(PickleState *state, PicklerObject *self, PyObject *obj) { @@ -4860,8 +4879,8 @@ dump(PickleState *state, PicklerObject *self, PyObject *obj) Py_XSETREF(self->persistent_id, tmp); /* Cache the reducer_override method, if it exists. */ - if (PyObject_GetOptionalAttr((PyObject *)self, &_Py_ID(reducer_override), - &tmp) < 0) { + if (pickler_get_optional_attr(self, &_Py_ID(reducer_override), + &tmp) < 0) { goto error; } Py_XSETREF(self->reducer_override, tmp); @@ -5141,8 +5160,8 @@ _pickle_Pickler___init___impl(PicklerObject *self, PyObject *file, self->fast_memo = NULL; if (self->dispatch_table == NULL) { - if (PyObject_GetOptionalAttr((PyObject *)self, &_Py_ID(dispatch_table), - &self->dispatch_table) < 0) { + if (pickler_get_optional_attr(self, &_Py_ID(dispatch_table), + &self->dispatch_table) < 0) { goto error; } }