Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-93955: Use unbound methods for slot __getattr__ and __getattribute__ #93956

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve performance of attribute lookups on objects with custom ``__getattribute__`` and ``__getattr__``. Patch by Ken Jin.
47 changes: 12 additions & 35 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -7782,61 +7782,38 @@ slot_tp_getattro(PyObject *self, PyObject *name)
return vectorcall_method(&_Py_ID(__getattribute__), stack, 2);
}

static PyObject *
call_attribute(PyObject *self, PyObject *attr, PyObject *name)
{
PyObject *res, *descr = NULL;
descrgetfunc f = Py_TYPE(attr)->tp_descr_get;

if (f != NULL) {
descr = f(attr, self, (PyObject *)(Py_TYPE(self)));
if (descr == NULL)
return NULL;
else
attr = descr;
}
res = PyObject_CallOneArg(attr, name);
Py_XDECREF(descr);
return res;
}

static PyObject *
slot_tp_getattr_hook(PyObject *self, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(self);
PyObject *getattr, *getattribute, *res;
PyObject *args[] = { self, name };
PyThreadState *tstate = _PyThreadState_GET();

/* speed hack: we could use lookup_maybe, but that would resolve the
method fully for each attribute lookup for classes with
__getattr__, even when the attribute is present. So we use
_PyType_Lookup and create the method only when needed, with
call_attribute. */
getattr = _PyType_Lookup(tp, &_Py_ID(__getattr__));
Fidget-Spinner marked this conversation as resolved.
Show resolved Hide resolved
int unbound_getattr;
getattr = lookup_maybe_method(self, &_Py_ID(__getattr__), &unbound_getattr);
if (getattr == NULL) {
/* No __getattr__ hook: use a simpler dispatcher */
tp->tp_getattro = slot_tp_getattro;
return slot_tp_getattro(self, name);
}
Py_INCREF(getattr);
/* speed hack: we could use lookup_maybe, but that would resolve the
method fully for each attribute lookup for classes with
__getattr__, even when self has the default __getattribute__
method. So we use _PyType_Lookup and create the method only when
needed, with call_attribute. */
getattribute = _PyType_Lookup(tp, &_Py_ID(__getattribute__));

int unbound_getattribute;
getattribute = lookup_maybe_method(self, &_Py_ID(__getattribute__),
&unbound_getattribute);
if (getattribute == NULL ||
(Py_IS_TYPE(getattribute, &PyWrapperDescr_Type) &&
((PyWrapperDescrObject *)getattribute)->d_wrapped ==
(void *)PyObject_GenericGetAttr))
res = PyObject_GenericGetAttr(self, name);
else {
Py_INCREF(getattribute);
res = call_attribute(self, getattribute, name);
Py_DECREF(getattribute);
res = vectorcall_unbound(tstate, unbound_getattribute, getattribute,
args, 2);
}
Py_XDECREF(getattribute);
if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
res = call_attribute(self, getattr, name);
res = vectorcall_unbound(tstate, unbound_getattr, getattr, args, 2);
}
Py_DECREF(getattr);
return res;
Expand Down