diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-20-12-11-01.gh-issue-157840.EAo1Kp.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-20-12-11-01.gh-issue-157840.EAo1Kp.rst new file mode 100644 index 00000000000000..c2212267ce6fa4 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-20-12-11-01.gh-issue-157840.EAo1Kp.rst @@ -0,0 +1,2 @@ +Speed up calls to :func:`hasattr` and three-argument :func:`getattr` for +unassigned attributes in an object's :attr:`~object.__slots__` by roughly 6x. diff --git a/Objects/object.c b/Objects/object.c index 971ac1b7a68669..c7aeba0cee22c6 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1924,6 +1924,19 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, if (descr != NULL) { f = Py_TYPE(descr)->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { + // gh-157840: We special-case member descriptors here to avoid + // allocating an extra AttributeError + if (suppress && Py_IS_TYPE(descr, &PyMemberDescr_Type)) { + PyMemberDef *member = ((PyMemberDescrObject *)descr)->d_member; + if (member->type == Py_T_OBJECT_EX + && !(member->flags & Py_AUDIT_READ) + && PyObject_TypeCheck(obj, PyDescr_TYPE(descr))) { + PyObject **addr = _PyMember_GetOffset(obj, member); + if (FT_ATOMIC_LOAD_PTR(*addr) == NULL) { + goto done; + } + } + } res = f(descr, obj, (PyObject *)Py_TYPE(obj)); if (res == NULL && suppress && PyErr_ExceptionMatches(PyExc_AttributeError)) {