From 378edee0a3b913d60653dc17dfe61d83405a8135 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 16 Jan 2018 20:52:41 +0900 Subject: [PATCH] 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. --- Include/object.h | 4 +- .../2018-01-16-18-51-58.bpo-32544.ga-cFE.rst | 3 + Modules/_threadmodule.c | 6 +- Objects/object.c | 56 +++++++++++++++++-- Python/bltinmodule.c | 21 +++---- 5 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-01-16-18-51-58.bpo-32544.ga-cFE.rst diff --git a/Include/object.h b/Include/object.h index 7db5bfea615..274b67656ee 100644 --- a/Include/object.h +++ b/Include/object.h @@ -536,6 +536,8 @@ PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *); #ifndef Py_LIMITED_API 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(int) _PyObject_SetAttrId(PyObject *, struct _Py_Identifier *, PyObject *); PyAPI_FUNC(int) _PyObject_HasAttrId(PyObject *, struct _Py_Identifier *); @@ -567,7 +569,7 @@ PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *); /* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes dict as the last parameter. */ PyAPI_FUNC(PyObject *) -_PyObject_GenericGetAttrWithDict(PyObject *, PyObject *, PyObject *); +_PyObject_GenericGetAttrWithDict(PyObject *, PyObject *, PyObject *, int); PyAPI_FUNC(int) _PyObject_GenericSetAttrWithDict(PyObject *, PyObject *, PyObject *, PyObject *); diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-01-16-18-51-58.bpo-32544.ga-cFE.rst b/Misc/NEWS.d/next/Core and Builtins/2018-01-16-18-51-58.bpo-32544.ga-cFE.rst new file mode 100644 index 00000000000..55a52fa9f1c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-01-16-18-51-58.bpo-32544.ga-cFE.rst @@ -0,0 +1,3 @@ +``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__``. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index c9171f52e38..f594bda678a 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -925,13 +925,15 @@ local_getattro(localobject *self, PyObject *name) if (Py_TYPE(self) != &localtype) /* 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 */ value = PyDict_GetItem(ldict, name); if (value == NULL) /* 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); return value; diff --git a/Objects/object.c b/Objects/object.c index a0d651d0805..62d7fbebf40 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -887,10 +887,41 @@ PyObject_GetAttr(PyObject *v, PyObject *name) 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 PyObject_HasAttr(PyObject *v, PyObject *name) { - PyObject *res = PyObject_GetAttr(v, name); + PyObject *res = _PyObject_GetAttrWithoutError(v, name); if (res != NULL) { Py_DECREF(res); return 1; @@ -1098,10 +1129,13 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) /* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */ 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 this method. + + When suppress=1, this function suppress AttributeError. */ PyTypeObject *tp = Py_TYPE(obj); @@ -1132,6 +1166,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { res = f(descr, obj, (PyObject *)obj->ob_type); + if (res == NULL && suppress && + PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + } goto done; } } @@ -1171,6 +1209,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) if (f != NULL) { res = f(descr, obj, (PyObject *)Py_TYPE(obj)); + if (res == NULL && suppress && + PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + } goto done; } @@ -1180,9 +1222,11 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) goto done; } - PyErr_Format(PyExc_AttributeError, - "'%.50s' object has no attribute '%U'", - tp->tp_name, name); + if (!suppress) { + PyErr_Format(PyExc_AttributeError, + "'%.50s' object has no attribute '%U'", + tp->tp_name, name); + } done: Py_XDECREF(descr); Py_DECREF(name); @@ -1192,7 +1236,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) PyObject * PyObject_GenericGetAttr(PyObject *obj, PyObject *name) { - return _PyObject_GenericGetAttrWithDict(obj, name, NULL); + return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 0); } int diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index e702f7c6e9e..844548f75fd 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1126,13 +1126,15 @@ builtin_getattr(PyObject *self, PyObject *const *args, Py_ssize_t nargs) "getattr(): attribute name must be string"); return NULL; } - result = PyObject_GetAttr(v, name); - if (result == NULL && dflt != NULL && - PyErr_ExceptionMatches(PyExc_AttributeError)) - { - PyErr_Clear(); - Py_INCREF(dflt); - result = dflt; + if (dflt != NULL) { + result = _PyObject_GetAttrWithoutError(v, name); + if (result == NULL && !PyErr_Occurred()) { + Py_INCREF(dflt); + return dflt; + } + } + else { + result = PyObject_GetAttr(v, name); } return result; } @@ -1189,10 +1191,9 @@ builtin_hasattr_impl(PyObject *module, PyObject *obj, PyObject *name) "hasattr(): attribute name must be string"); return NULL; } - v = PyObject_GetAttr(obj, name); + v = _PyObject_GetAttrWithoutError(obj, name); if (v == NULL) { - if (PyErr_ExceptionMatches(PyExc_AttributeError)) { - PyErr_Clear(); + if (!PyErr_Occurred()) { Py_RETURN_FALSE; } return NULL;