[Python-checkins] gh-104066: Improve performance of hasattr for module objects (#104063)
carljm
webhook-mailer at python.org
Thu May 4 10:50:46 EDT 2023
https://github.com/python/cpython/commit/fdcb49c36b2ed8347d8d9f2dcd7052cc90207beb
commit: fdcb49c36b2ed8347d8d9f2dcd7052cc90207beb
branch: main
author: Itamar Ostricher <itamarost at gmail.com>
committer: carljm <carl at oddbird.net>
date: 2023-05-04T08:50:26-06:00
summary:
gh-104066: Improve performance of hasattr for module objects (#104063)
files:
A Misc/NEWS.d/next/Core and Builtins/2023-05-01-14-48-29.gh-issue-104066.pzoUZQ.rst
M Include/internal/pycore_moduleobject.h
M Objects/moduleobject.c
M Objects/object.c
diff --git a/Include/internal/pycore_moduleobject.h b/Include/internal/pycore_moduleobject.h
index 76361b8dff11..15a1bcb6ae51 100644
--- a/Include/internal/pycore_moduleobject.h
+++ b/Include/internal/pycore_moduleobject.h
@@ -36,6 +36,9 @@ static inline PyObject* _PyModule_GetDict(PyObject *mod) {
return dict;
}
+PyObject* _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress);
+PyObject* _Py_module_getattro(PyModuleObject *m, PyObject *name);
+
#ifdef __cplusplus
}
#endif
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-01-14-48-29.gh-issue-104066.pzoUZQ.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-01-14-48-29.gh-issue-104066.pzoUZQ.rst
new file mode 100644
index 000000000000..97e0c01689cb
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-01-14-48-29.gh-issue-104066.pzoUZQ.rst
@@ -0,0 +1,2 @@
+Improve the performance of :func:`hasattr` for module objects with a missing
+attribute.
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c
index a0be19a3ca8a..ffcd90ed122e 100644
--- a/Objects/moduleobject.c
+++ b/Objects/moduleobject.c
@@ -702,7 +702,11 @@ int
_PyModuleSpec_IsInitializing(PyObject *spec)
{
if (spec != NULL) {
- PyObject *value = PyObject_GetAttr(spec, &_Py_ID(_initializing));
+ PyObject *value;
+ int ok = _PyObject_LookupAttr(spec, &_Py_ID(_initializing), &value);
+ if (ok == 0) {
+ return 0;
+ }
if (value != NULL) {
int initializing = PyObject_IsTrue(value);
Py_DECREF(value);
@@ -738,19 +742,37 @@ _PyModuleSpec_IsUninitializedSubmodule(PyObject *spec, PyObject *name)
return is_uninitialized;
}
-static PyObject*
-module_getattro(PyModuleObject *m, PyObject *name)
+PyObject*
+_Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
{
+ // When suppress=1, this function suppresses AttributeError.
PyObject *attr, *mod_name, *getattr;
- attr = PyObject_GenericGetAttr((PyObject *)m, name);
- if (attr || !PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress);
+ if (attr) {
return attr;
}
- PyErr_Clear();
+ if (suppress == 1) {
+ if (PyErr_Occurred()) {
+ // pass up non-AttributeError exception
+ return NULL;
+ }
+ }
+ else {
+ if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ // pass up non-AttributeError exception
+ return NULL;
+ }
+ PyErr_Clear();
+ }
assert(m->md_dict != NULL);
getattr = PyDict_GetItemWithError(m->md_dict, &_Py_ID(__getattr__));
if (getattr) {
- return PyObject_CallOneArg(getattr, name);
+ PyObject *result = PyObject_CallOneArg(getattr, name);
+ if (result == NULL && suppress == 1 && PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ // suppress AttributeError
+ PyErr_Clear();
+ }
+ return result;
}
if (PyErr_Occurred()) {
return NULL;
@@ -763,37 +785,48 @@ module_getattro(PyModuleObject *m, PyObject *name)
Py_DECREF(mod_name);
return NULL;
}
- Py_XINCREF(spec);
- if (_PyModuleSpec_IsInitializing(spec)) {
- PyErr_Format(PyExc_AttributeError,
- "partially initialized "
- "module '%U' has no attribute '%U' "
- "(most likely due to a circular import)",
- mod_name, name);
- }
- else if (_PyModuleSpec_IsUninitializedSubmodule(spec, name)) {
- PyErr_Format(PyExc_AttributeError,
- "cannot access submodule '%U' of module '%U' "
- "(most likely due to a circular import)",
- name, mod_name);
- }
- else {
- PyErr_Format(PyExc_AttributeError,
- "module '%U' has no attribute '%U'",
- mod_name, name);
+ if (suppress != 1) {
+ Py_XINCREF(spec);
+ if (_PyModuleSpec_IsInitializing(spec)) {
+ PyErr_Format(PyExc_AttributeError,
+ "partially initialized "
+ "module '%U' has no attribute '%U' "
+ "(most likely due to a circular import)",
+ mod_name, name);
+ }
+ else if (_PyModuleSpec_IsUninitializedSubmodule(spec, name)) {
+ PyErr_Format(PyExc_AttributeError,
+ "cannot access submodule '%U' of module '%U' "
+ "(most likely due to a circular import)",
+ name, mod_name);
+ }
+ else {
+ PyErr_Format(PyExc_AttributeError,
+ "module '%U' has no attribute '%U'",
+ mod_name, name);
+ }
+ Py_XDECREF(spec);
}
- Py_XDECREF(spec);
Py_DECREF(mod_name);
return NULL;
}
else if (PyErr_Occurred()) {
return NULL;
}
- PyErr_Format(PyExc_AttributeError,
- "module has no attribute '%U'", name);
+ if (suppress != 1) {
+ PyErr_Format(PyExc_AttributeError,
+ "module has no attribute '%U'", name);
+ }
return NULL;
}
+
+PyObject*
+_Py_module_getattro(PyModuleObject *m, PyObject *name)
+{
+ return _Py_module_getattro_impl(m, name, 0);
+}
+
static int
module_traverse(PyModuleObject *m, visitproc visit, void *arg)
{
@@ -951,7 +984,7 @@ PyTypeObject PyModule_Type = {
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
- (getattrofunc)module_getattro, /* tp_getattro */
+ (getattrofunc)_Py_module_getattro, /* tp_getattro */
PyObject_GenericSetAttr, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
diff --git a/Objects/object.c b/Objects/object.c
index c6ef59281648..41c52e21045a 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1085,6 +1085,17 @@ _PyObject_LookupAttr(PyObject *v, PyObject *name, PyObject **result)
return 0;
}
}
+ else if (tp->tp_getattro == (getattrofunc)_Py_module_getattro) {
+ // optimization: suppress attribute error from module getattro method
+ *result = _Py_module_getattro_impl((PyModuleObject*)v, name, 1);
+ if (*result != NULL) {
+ return 1;
+ }
+ if (PyErr_Occurred()) {
+ return -1;
+ }
+ return 0;
+ }
else if (tp->tp_getattro != NULL) {
*result = (*tp->tp_getattro)(v, name);
}
More information about the Python-checkins
mailing list