[Python-checkins] bpo-32544: Speed up hasattr() and getattr() (GH-5173)

INADA Naoki webhook-mailer at python.org
Tue Jan 16 06:52:48 EST 2018


https://github.com/python/cpython/commit/378edee0a3b913d60653dc17dfe61d83405a8135
commit: 378edee0a3b913d60653dc17dfe61d83405a8135
branch: master
author: INADA Naoki <methane at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2018-01-16T20:52:41+09:00
summary:

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.

files:
A Misc/NEWS.d/next/Core and Builtins/2018-01-16-18-51-58.bpo-32544.ga-cFE.rst
M Include/object.h
M Modules/_threadmodule.c
M Objects/object.c
M Python/bltinmodule.c

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;



More information about the Python-checkins mailing list