Skip to content

Commit 378edee

Browse files
authored
bpo-32544: Speed up hasattr() and getattr() (GH-5173)
AttributeError was raised always when attribute is not found. This commit skip raising AttributeError when `tp_getattro` is `PyObject_GenericGetAttr`. It makes hasattr() and getattr() about 4x faster when attribute is not found.
1 parent b44c516 commit 378edee

File tree

5 files changed

+71
-19
lines changed

5 files changed

+71
-19
lines changed

Include/object.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *);
536536
PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *);
537537
#ifndef Py_LIMITED_API
538538
PyAPI_FUNC(int) _PyObject_IsAbstract(PyObject *);
539+
/* Same as PyObject_GetAttr(), but don't raise AttributeError. */
540+
PyAPI_FUNC(PyObject *) _PyObject_GetAttrWithoutError(PyObject *, PyObject *);
539541
PyAPI_FUNC(PyObject *) _PyObject_GetAttrId(PyObject *, struct _Py_Identifier *);
540542
PyAPI_FUNC(int) _PyObject_SetAttrId(PyObject *, struct _Py_Identifier *, PyObject *);
541543
PyAPI_FUNC(int) _PyObject_HasAttrId(PyObject *, struct _Py_Identifier *);
@@ -567,7 +569,7 @@ PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *);
567569
/* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes
568570
dict as the last parameter. */
569571
PyAPI_FUNC(PyObject *)
570-
_PyObject_GenericGetAttrWithDict(PyObject *, PyObject *, PyObject *);
572+
_PyObject_GenericGetAttrWithDict(PyObject *, PyObject *, PyObject *, int);
571573
PyAPI_FUNC(int)
572574
_PyObject_GenericSetAttrWithDict(PyObject *, PyObject *,
573575
PyObject *, PyObject *);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``hasattr(obj, name)`` and ``getattr(obj, name, default)`` are about 4 times
2+
faster than before when ``name`` is not found and ``obj`` doesn't override
3+
``__getattr__`` or ``__getattribute__``.

Modules/_threadmodule.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -925,13 +925,15 @@ local_getattro(localobject *self, PyObject *name)
925925

926926
if (Py_TYPE(self) != &localtype)
927927
/* use generic lookup for subtypes */
928-
return _PyObject_GenericGetAttrWithDict((PyObject *)self, name, ldict);
928+
return _PyObject_GenericGetAttrWithDict(
929+
(PyObject *)self, name, ldict, 0);
929930

930931
/* Optimization: just look in dict ourselves */
931932
value = PyDict_GetItem(ldict, name);
932933
if (value == NULL)
933934
/* Fall back on generic to get __class__ and __dict__ */
934-
return _PyObject_GenericGetAttrWithDict((PyObject *)self, name, ldict);
935+
return _PyObject_GenericGetAttrWithDict(
936+
(PyObject *)self, name, ldict, 0);
935937

936938
Py_INCREF(value);
937939
return value;

Objects/object.c

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -887,10 +887,41 @@ PyObject_GetAttr(PyObject *v, PyObject *name)
887887
return NULL;
888888
}
889889

890+
PyObject *
891+
_PyObject_GetAttrWithoutError(PyObject *v, PyObject *name)
892+
{
893+
PyTypeObject *tp = Py_TYPE(v);
894+
PyObject *ret = NULL;
895+
896+
if (!PyUnicode_Check(name)) {
897+
PyErr_Format(PyExc_TypeError,
898+
"attribute name must be string, not '%.200s'",
899+
name->ob_type->tp_name);
900+
return NULL;
901+
}
902+
903+
if (tp->tp_getattro == PyObject_GenericGetAttr) {
904+
return _PyObject_GenericGetAttrWithDict(v, name, NULL, 1);
905+
}
906+
if (tp->tp_getattro != NULL) {
907+
ret = (*tp->tp_getattro)(v, name);
908+
}
909+
else if (tp->tp_getattr != NULL) {
910+
const char *name_str = PyUnicode_AsUTF8(name);
911+
if (name_str == NULL)
912+
return NULL;
913+
ret = (*tp->tp_getattr)(v, (char *)name_str);
914+
}
915+
if (ret == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
916+
PyErr_Clear();
917+
}
918+
return ret;
919+
}
920+
890921
int
891922
PyObject_HasAttr(PyObject *v, PyObject *name)
892923
{
893-
PyObject *res = PyObject_GetAttr(v, name);
924+
PyObject *res = _PyObject_GetAttrWithoutError(v, name);
894925
if (res != NULL) {
895926
Py_DECREF(res);
896927
return 1;
@@ -1098,10 +1129,13 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
10981129
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */
10991130

11001131
PyObject *
1101-
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
1132+
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
1133+
PyObject *dict, int suppress)
11021134
{
11031135
/* Make sure the logic of _PyObject_GetMethod is in sync with
11041136
this method.
1137+
1138+
When suppress=1, this function suppress AttributeError.
11051139
*/
11061140

11071141
PyTypeObject *tp = Py_TYPE(obj);
@@ -1132,6 +1166,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
11321166
f = descr->ob_type->tp_descr_get;
11331167
if (f != NULL && PyDescr_IsData(descr)) {
11341168
res = f(descr, obj, (PyObject *)obj->ob_type);
1169+
if (res == NULL && suppress &&
1170+
PyErr_ExceptionMatches(PyExc_AttributeError)) {
1171+
PyErr_Clear();
1172+
}
11351173
goto done;
11361174
}
11371175
}
@@ -1171,6 +1209,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
11711209

11721210
if (f != NULL) {
11731211
res = f(descr, obj, (PyObject *)Py_TYPE(obj));
1212+
if (res == NULL && suppress &&
1213+
PyErr_ExceptionMatches(PyExc_AttributeError)) {
1214+
PyErr_Clear();
1215+
}
11741216
goto done;
11751217
}
11761218

@@ -1180,9 +1222,11 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
11801222
goto done;
11811223
}
11821224

1183-
PyErr_Format(PyExc_AttributeError,
1184-
"'%.50s' object has no attribute '%U'",
1185-
tp->tp_name, name);
1225+
if (!suppress) {
1226+
PyErr_Format(PyExc_AttributeError,
1227+
"'%.50s' object has no attribute '%U'",
1228+
tp->tp_name, name);
1229+
}
11861230
done:
11871231
Py_XDECREF(descr);
11881232
Py_DECREF(name);
@@ -1192,7 +1236,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
11921236
PyObject *
11931237
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
11941238
{
1195-
return _PyObject_GenericGetAttrWithDict(obj, name, NULL);
1239+
return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 0);
11961240
}
11971241

11981242
int

Python/bltinmodule.c

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,13 +1126,15 @@ builtin_getattr(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
11261126
"getattr(): attribute name must be string");
11271127
return NULL;
11281128
}
1129-
result = PyObject_GetAttr(v, name);
1130-
if (result == NULL && dflt != NULL &&
1131-
PyErr_ExceptionMatches(PyExc_AttributeError))
1132-
{
1133-
PyErr_Clear();
1134-
Py_INCREF(dflt);
1135-
result = dflt;
1129+
if (dflt != NULL) {
1130+
result = _PyObject_GetAttrWithoutError(v, name);
1131+
if (result == NULL && !PyErr_Occurred()) {
1132+
Py_INCREF(dflt);
1133+
return dflt;
1134+
}
1135+
}
1136+
else {
1137+
result = PyObject_GetAttr(v, name);
11361138
}
11371139
return result;
11381140
}
@@ -1189,10 +1191,9 @@ builtin_hasattr_impl(PyObject *module, PyObject *obj, PyObject *name)
11891191
"hasattr(): attribute name must be string");
11901192
return NULL;
11911193
}
1192-
v = PyObject_GetAttr(obj, name);
1194+
v = _PyObject_GetAttrWithoutError(obj, name);
11931195
if (v == NULL) {
1194-
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
1195-
PyErr_Clear();
1196+
if (!PyErr_Occurred()) {
11961197
Py_RETURN_FALSE;
11971198
}
11981199
return NULL;

0 commit comments

Comments
 (0)