From f2e8d3485c42a64c2cc40d850d024d0b6bbff235 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 14 Jan 2018 02:21:16 +0900 Subject: [PATCH 1/7] Make hasattr() and getattr(k,v,dflt) faster when not found. --- Include/object.h | 4 +++- Modules/_threadmodule.c | 6 ++++-- Objects/object.c | 44 ++++++++++++++++++++++++++++++++++++----- Python/bltinmodule.c | 21 ++++++++++---------- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/Include/object.h b/Include/object.h index 7db5bfea615e98..9c8279266052d5 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 to 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/Modules/_threadmodule.c b/Modules/_threadmodule.c index c9171f52e385cc..f594bda678a8ac 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 a0d651d0805a0b..f2b8788765914d 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -887,6 +887,37 @@ 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 != NULL) { + // Fast path, skip raising AttributeError + if (tp->tp_getattro == PyObject_GenericGetAttr) { + return _PyObject_GenericGetAttrWithDict(v, name, NULL, 1); + } + 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) { @@ -1098,7 +1129,8 @@ _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. @@ -1180,9 +1212,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 +1226,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 e702f7c6e9e5a2..844548f75fd528 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; From 54c4cf5c32c66b12c1cff9e5db7fd4ecbdb97cfd Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 14 Jan 2018 03:10:55 +0900 Subject: [PATCH 2/7] Fix --- Objects/object.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index f2b8788765914d..ac7434642a6ae1 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -900,11 +900,12 @@ _PyObject_GetAttrWithoutError(PyObject *v, PyObject *name) return NULL; } if (tp->tp_getattro != NULL) { - // Fast path, skip raising AttributeError if (tp->tp_getattro == PyObject_GenericGetAttr) { - return _PyObject_GenericGetAttrWithDict(v, name, NULL, 1); + ret = _PyObject_GenericGetAttrWithDict(v, name, NULL, 1); + } + else { + ret = (*tp->tp_getattro)(v, name); } - ret = (*tp->tp_getattro)(v, name); } else if (tp->tp_getattr != NULL) { const char *name_str = PyUnicode_AsUTF8(name); @@ -1134,6 +1135,9 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, { /* Make sure the logic of _PyObject_GetMethod is in sync with this method. + + When suppress=1, this function doesn't raise AttributeError. + But descriptors may raise it. */ PyTypeObject *tp = Py_TYPE(obj); From fc689f7e9a9296c4e78f63ddc986bcd3021f3ef9 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 15 Jan 2018 15:06:03 +0900 Subject: [PATCH 3/7] PyObject_HasAttr() uses _PyObject_GetAttrWithoutError() too. --- Objects/object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/object.c b/Objects/object.c index ac7434642a6ae1..aa72974371401c 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -922,7 +922,7 @@ _PyObject_GetAttrWithoutError(PyObject *v, PyObject *name) 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; From 4a294056c02f8788d321594bb0305c2c848fbcf8 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 15 Jan 2018 18:11:48 +0900 Subject: [PATCH 4/7] fix comment --- Include/object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/object.h b/Include/object.h index 9c8279266052d5..274b67656ee9ef 100644 --- a/Include/object.h +++ b/Include/object.h @@ -536,7 +536,7 @@ 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 to PyObject_GetAttr(), but don't raise AttributeError. */ +/* 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 *); From 477544cfbbaeabc505dfcf7654880154448bc850 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 16 Jan 2018 00:47:04 +0900 Subject: [PATCH 5/7] _PyObject_GenericGetAttrWithDict() suppress AttirubteError from descriptor. --- Objects/object.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index aa72974371401c..62d7fbebf40d59 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -899,13 +899,12 @@ _PyObject_GetAttrWithoutError(PyObject *v, PyObject *name) 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) { - if (tp->tp_getattro == PyObject_GenericGetAttr) { - ret = _PyObject_GenericGetAttrWithDict(v, name, NULL, 1); - } - else { - ret = (*tp->tp_getattro)(v, name); - } + ret = (*tp->tp_getattro)(v, name); } else if (tp->tp_getattr != NULL) { const char *name_str = PyUnicode_AsUTF8(name); @@ -1136,8 +1135,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, /* Make sure the logic of _PyObject_GetMethod is in sync with this method. - When suppress=1, this function doesn't raise AttributeError. - But descriptors may raise it. + When suppress=1, this function suppress AttributeError. */ PyTypeObject *tp = Py_TYPE(obj); @@ -1168,6 +1166,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, 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; } } @@ -1207,6 +1209,10 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, if (f != NULL) { res = f(descr, obj, (PyObject *)Py_TYPE(obj)); + if (res == NULL && suppress && + PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + } goto done; } From 6de7b008e5a21e2e834865f349aca724e37aa70b Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 16 Jan 2018 18:52:03 +0900 Subject: [PATCH 6/7] Add NEWS entry --- .../Core and Builtins/2018-01-16-18-51-58.bpo-32544.ga-cFE.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-01-16-18-51-58.bpo-32544.ga-cFE.rst 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 00000000000000..a519587a5e33de --- /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)`` is about 4 times +faster than before when ``name`` is not found and ``obj`` doesn't override +``__getattr__`` or ``__getattribute__``. From 3ee971ab8648d8db4acc0aca39653a4f693b35c4 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 16 Jan 2018 19:09:34 +0900 Subject: [PATCH 7/7] s/is/are/ --- .../Core and Builtins/2018-01-16-18-51-58.bpo-32544.ga-cFE.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a519587a5e33de..55a52fa9f1c058 100644 --- 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 @@ -1,3 +1,3 @@ -``hasattr(obj, name)`` and ``getattr(obj, name, default)`` is about 4 times +``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__``.