Skip to content

Commit bb05f55

Browse files
committed
Faster attribute lookup
When an attribute lookup fails, Python spends a significant chunk of CPU cycles formatting and then raising an exception. The ``nb::getattr(object, key, default)`` API provides a fallback ``default`` value in the case of an error. The expense of raising an exception is therefore not wanted here. This commit avoids this exception by - using the new ``PyObject_GetOptionalAttr()`` function on Python 3.13+ - using an internal ``_PyObject_LookupAttr()`` API before Python 3.13. - on the 3.12 stable API, using ``PyObject_HasAttr()`` before doing the lookup.
1 parent ab05b01 commit bb05f55

File tree

2 files changed

+47
-7
lines changed

2 files changed

+47
-7
lines changed

cmake/darwin-ld-cpython.sym

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,7 @@
867867
-U __PyObject_GC_New
868868
-U __PyObject_GC_NewVar
869869
-U __PyObject_GC_Resize
870+
-U __PyObject_LookupAttr
870871
-U __PyObject_MakeTpCall
871872
-U __PyObject_New
872873
-U __PyObject_NewVar

src/common.cpp

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -371,20 +371,59 @@ PyObject *getattr(PyObject *obj, PyObject *key) {
371371
return res;
372372
}
373373

374-
PyObject *getattr(PyObject *obj, const char *key, PyObject *def) noexcept {
375-
PyObject *res = PyObject_GetAttrString(obj, key);
376-
if (res)
374+
PyObject *getattr(PyObject *obj, const char *key_, PyObject *def) noexcept {
375+
#if (defined(Py_LIMITED_API) && PY_LIMITED_API < 0x030d0000) || defined(PYPY_VERSION)
376+
str key(key_);
377+
if (PyObject_HasAttr(obj, key.ptr())) {
378+
PyObject *res = PyObject_GetAttr(obj, key.ptr());
379+
if (res)
380+
return res;
381+
PyErr_Clear();
382+
}
383+
#else
384+
PyObject *res;
385+
int rv;
386+
387+
#if PY_VERSION_HEX < 0x030d0000
388+
rv = _PyObject_LookupAttr(obj, str(key_).ptr(), &res);
389+
#else
390+
rv = PyObject_GetOptionalAttrString(obj, key_, &res);
391+
#endif
392+
393+
if (rv == 1)
377394
return res;
378-
PyErr_Clear();
395+
else if (rv < 0)
396+
PyErr_Clear();
397+
#endif
398+
379399
Py_XINCREF(def);
380400
return def;
381401
}
382402

383403
PyObject *getattr(PyObject *obj, PyObject *key, PyObject *def) noexcept {
384-
PyObject *res = PyObject_GetAttr(obj, key);
385-
if (res)
404+
#if (defined(Py_LIMITED_API) && PY_LIMITED_API < 0x030d0000) || defined(PYPY_VERSION)
405+
if (PyObject_HasAttr(obj, key)) {
406+
PyObject *res = PyObject_GetAttr(obj, key);
407+
if (res)
408+
return res;
409+
PyErr_Clear();
410+
}
411+
#else
412+
PyObject *res;
413+
int rv;
414+
415+
#if PY_VERSION_HEX < 0x030d0000
416+
rv = _PyObject_LookupAttr(obj, key, &res);
417+
#else
418+
rv = PyObject_GetOptionalAttr(obj, key, &res);
419+
#endif
420+
421+
if (rv == 1)
386422
return res;
387-
PyErr_Clear();
423+
else if (rv < 0)
424+
PyErr_Clear();
425+
#endif
426+
388427
Py_XINCREF(def);
389428
return def;
390429
}

0 commit comments

Comments
 (0)