Commit 378edee0 authored by INADA Naoki's avatar INADA Naoki Committed by GitHub

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.
parent b44c5169
...@@ -536,6 +536,8 @@ PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *); ...@@ -536,6 +536,8 @@ PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *);
PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *);
#ifndef Py_LIMITED_API #ifndef Py_LIMITED_API
PyAPI_FUNC(int) _PyObject_IsAbstract(PyObject *); PyAPI_FUNC(int) _PyObject_IsAbstract(PyObject *);
/* Same as PyObject_GetAttr(), but don't raise AttributeError. */
PyAPI_FUNC(PyObject *) _PyObject_GetAttrWithoutError(PyObject *, PyObject *);
PyAPI_FUNC(PyObject *) _PyObject_GetAttrId(PyObject *, struct _Py_Identifier *); PyAPI_FUNC(PyObject *) _PyObject_GetAttrId(PyObject *, struct _Py_Identifier *);
PyAPI_FUNC(int) _PyObject_SetAttrId(PyObject *, struct _Py_Identifier *, PyObject *); PyAPI_FUNC(int) _PyObject_SetAttrId(PyObject *, struct _Py_Identifier *, PyObject *);
PyAPI_FUNC(int) _PyObject_HasAttrId(PyObject *, struct _Py_Identifier *); PyAPI_FUNC(int) _PyObject_HasAttrId(PyObject *, struct _Py_Identifier *);
...@@ -567,7 +569,7 @@ PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *); ...@@ -567,7 +569,7 @@ PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *);
/* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes /* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes
dict as the last parameter. */ dict as the last parameter. */
PyAPI_FUNC(PyObject *) PyAPI_FUNC(PyObject *)
_PyObject_GenericGetAttrWithDict(PyObject *, PyObject *, PyObject *); _PyObject_GenericGetAttrWithDict(PyObject *, PyObject *, PyObject *, int);
PyAPI_FUNC(int) PyAPI_FUNC(int)
_PyObject_GenericSetAttrWithDict(PyObject *, PyObject *, _PyObject_GenericSetAttrWithDict(PyObject *, PyObject *,
PyObject *, PyObject *); PyObject *, PyObject *);
......
``hasattr(obj, name)`` and ``getattr(obj, name, default)`` are about 4 times
faster than before when ``name`` is not found and ``obj`` doesn't override
``__getattr__`` or ``__getattribute__``.
...@@ -925,13 +925,15 @@ local_getattro(localobject *self, PyObject *name) ...@@ -925,13 +925,15 @@ local_getattro(localobject *self, PyObject *name)
if (Py_TYPE(self) != &localtype) if (Py_TYPE(self) != &localtype)
/* use generic lookup for subtypes */ /* use generic lookup for subtypes */
return _PyObject_GenericGetAttrWithDict((PyObject *)self, name, ldict); return _PyObject_GenericGetAttrWithDict(
(PyObject *)self, name, ldict, 0);
/* Optimization: just look in dict ourselves */ /* Optimization: just look in dict ourselves */
value = PyDict_GetItem(ldict, name); value = PyDict_GetItem(ldict, name);
if (value == NULL) if (value == NULL)
/* Fall back on generic to get __class__ and __dict__ */ /* Fall back on generic to get __class__ and __dict__ */
return _PyObject_GenericGetAttrWithDict((PyObject *)self, name, ldict); return _PyObject_GenericGetAttrWithDict(
(PyObject *)self, name, ldict, 0);
Py_INCREF(value); Py_INCREF(value);
return value; return value;
......
...@@ -887,10 +887,41 @@ PyObject_GetAttr(PyObject *v, PyObject *name) ...@@ -887,10 +887,41 @@ PyObject_GetAttr(PyObject *v, PyObject *name)
return NULL; return NULL;
} }
PyObject *
_PyObject_GetAttrWithoutError(PyObject *v, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(v);
PyObject *ret = NULL;
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"attribute name must be string, not '%.200s'",
name->ob_type->tp_name);
return NULL;
}
if (tp->tp_getattro == PyObject_GenericGetAttr) {
return _PyObject_GenericGetAttrWithDict(v, name, NULL, 1);
}
if (tp->tp_getattro != NULL) {
ret = (*tp->tp_getattro)(v, name);
}
else if (tp->tp_getattr != NULL) {
const char *name_str = PyUnicode_AsUTF8(name);
if (name_str == NULL)
return NULL;
ret = (*tp->tp_getattr)(v, (char *)name_str);
}
if (ret == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
}
return ret;
}
int int
PyObject_HasAttr(PyObject *v, PyObject *name) PyObject_HasAttr(PyObject *v, PyObject *name)
{ {
PyObject *res = PyObject_GetAttr(v, name); PyObject *res = _PyObject_GetAttrWithoutError(v, name);
if (res != NULL) { if (res != NULL) {
Py_DECREF(res); Py_DECREF(res);
return 1; return 1;
...@@ -1098,10 +1129,13 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) ...@@ -1098,10 +1129,13 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */ /* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */
PyObject * PyObject *
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
PyObject *dict, int suppress)
{ {
/* Make sure the logic of _PyObject_GetMethod is in sync with /* Make sure the logic of _PyObject_GetMethod is in sync with
this method. this method.
When suppress=1, this function suppress AttributeError.
*/ */
PyTypeObject *tp = Py_TYPE(obj); PyTypeObject *tp = Py_TYPE(obj);
...@@ -1132,6 +1166,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) ...@@ -1132,6 +1166,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
f = descr->ob_type->tp_descr_get; f = descr->ob_type->tp_descr_get;
if (f != NULL && PyDescr_IsData(descr)) { if (f != NULL && PyDescr_IsData(descr)) {
res = f(descr, obj, (PyObject *)obj->ob_type); res = f(descr, obj, (PyObject *)obj->ob_type);
if (res == NULL && suppress &&
PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
}
goto done; goto done;
} }
} }
...@@ -1171,6 +1209,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) ...@@ -1171,6 +1209,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
if (f != NULL) { if (f != NULL) {
res = f(descr, obj, (PyObject *)Py_TYPE(obj)); res = f(descr, obj, (PyObject *)Py_TYPE(obj));
if (res == NULL && suppress &&
PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
}
goto done; goto done;
} }
...@@ -1180,9 +1222,11 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) ...@@ -1180,9 +1222,11 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
goto done; goto done;
} }
if (!suppress) {
PyErr_Format(PyExc_AttributeError, PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'", "'%.50s' object has no attribute '%U'",
tp->tp_name, name); tp->tp_name, name);
}
done: done:
Py_XDECREF(descr); Py_XDECREF(descr);
Py_DECREF(name); Py_DECREF(name);
...@@ -1192,7 +1236,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) ...@@ -1192,7 +1236,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
PyObject * PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name) PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{ {
return _PyObject_GenericGetAttrWithDict(obj, name, NULL); return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 0);
} }
int int
......
...@@ -1126,13 +1126,15 @@ builtin_getattr(PyObject *self, PyObject *const *args, Py_ssize_t nargs) ...@@ -1126,13 +1126,15 @@ builtin_getattr(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
"getattr(): attribute name must be string"); "getattr(): attribute name must be string");
return NULL; return NULL;
} }
result = PyObject_GetAttr(v, name); if (dflt != NULL) {
if (result == NULL && dflt != NULL && result = _PyObject_GetAttrWithoutError(v, name);
PyErr_ExceptionMatches(PyExc_AttributeError)) if (result == NULL && !PyErr_Occurred()) {
{
PyErr_Clear();
Py_INCREF(dflt); Py_INCREF(dflt);
result = dflt; return dflt;
}
}
else {
result = PyObject_GetAttr(v, name);
} }
return result; return result;
} }
...@@ -1189,10 +1191,9 @@ builtin_hasattr_impl(PyObject *module, PyObject *obj, PyObject *name) ...@@ -1189,10 +1191,9 @@ builtin_hasattr_impl(PyObject *module, PyObject *obj, PyObject *name)
"hasattr(): attribute name must be string"); "hasattr(): attribute name must be string");
return NULL; return NULL;
} }
v = PyObject_GetAttr(obj, name); v = _PyObject_GetAttrWithoutError(obj, name);
if (v == NULL) { if (v == NULL) {
if (PyErr_ExceptionMatches(PyExc_AttributeError)) { if (!PyErr_Occurred()) {
PyErr_Clear();
Py_RETURN_FALSE; Py_RETURN_FALSE;
} }
return NULL; return NULL;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment