Skip to content

Commit 7fc7909

Browse files
authored
pythongh-92216: improve performance of hasattr for type objects (pythonGH-99979)
1 parent 49f6ff7 commit 7fc7909

File tree

4 files changed

+44
-8
lines changed

4 files changed

+44
-8
lines changed

Include/internal/pycore_typeobject.h

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ extern static_builtin_state * _PyStaticType_GetState(PyTypeObject *);
7474
extern void _PyStaticType_ClearWeakRefs(PyTypeObject *type);
7575
extern void _PyStaticType_Dealloc(PyTypeObject *type);
7676

77+
PyObject *
78+
_Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int *suppress_missing_attribute);
79+
PyObject *
80+
_Py_type_getattro(PyTypeObject *type, PyObject *name);
7781

7882
PyObject *_Py_slot_tp_getattro(PyObject *self, PyObject *name);
7983
PyObject *_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve the performance of :func:`hasattr` for type objects with a missing attribute.

Objects/object.c

+9-1
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,15 @@ _PyObject_LookupAttr(PyObject *v, PyObject *name, PyObject **result)
939939
}
940940
return 0;
941941
}
942-
if (tp->tp_getattro != NULL) {
942+
if (tp->tp_getattro == (getattrofunc)_Py_type_getattro) {
943+
int supress_missing_attribute_exception = 0;
944+
*result = _Py_type_getattro_impl((PyTypeObject*)v, name, &supress_missing_attribute_exception);
945+
if (supress_missing_attribute_exception) {
946+
// return 0 without having to clear the exception
947+
return 0;
948+
}
949+
}
950+
else if (tp->tp_getattro != NULL) {
943951
*result = (*tp->tp_getattro)(v, name);
944952
}
945953
else if (tp->tp_getattr != NULL) {

Objects/typeobject.c

+30-7
Original file line numberDiff line numberDiff line change
@@ -4219,9 +4219,19 @@ _PyType_LookupId(PyTypeObject *type, _Py_Identifier *name)
42194219
}
42204220

42214221
/* This is similar to PyObject_GenericGetAttr(),
4222-
but uses _PyType_Lookup() instead of just looking in type->tp_dict. */
4223-
static PyObject *
4224-
type_getattro(PyTypeObject *type, PyObject *name)
4222+
but uses _PyType_Lookup() instead of just looking in type->tp_dict.
4223+
4224+
The argument suppress_missing_attribute is used to provide a
4225+
fast path for hasattr. The possible values are:
4226+
4227+
* NULL: do not suppress the exception
4228+
* Non-zero pointer: suppress the PyExc_AttributeError and
4229+
set *suppress_missing_attribute to 1 to signal we are returning NULL while
4230+
having suppressed the exception (other exceptions are not suppressed)
4231+
4232+
*/
4233+
PyObject *
4234+
_Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int * suppress_missing_attribute)
42254235
{
42264236
PyTypeObject *metatype = Py_TYPE(type);
42274237
PyObject *meta_attribute, *attribute;
@@ -4301,12 +4311,25 @@ type_getattro(PyTypeObject *type, PyObject *name)
43014311
}
43024312

43034313
/* Give up */
4304-
PyErr_Format(PyExc_AttributeError,
4305-
"type object '%.50s' has no attribute '%U'",
4306-
type->tp_name, name);
4314+
if (suppress_missing_attribute == NULL) {
4315+
PyErr_Format(PyExc_AttributeError,
4316+
"type object '%.50s' has no attribute '%U'",
4317+
type->tp_name, name);
4318+
} else {
4319+
// signal the caller we have not set an PyExc_AttributeError and gave up
4320+
*suppress_missing_attribute = 1;
4321+
}
43074322
return NULL;
43084323
}
43094324

4325+
/* This is similar to PyObject_GenericGetAttr(),
4326+
but uses _PyType_Lookup() instead of just looking in type->tp_dict. */
4327+
PyObject *
4328+
_Py_type_getattro(PyTypeObject *type, PyObject *name)
4329+
{
4330+
return _Py_type_getattro_impl(type, name, NULL);
4331+
}
4332+
43104333
static int
43114334
type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
43124335
{
@@ -4798,7 +4821,7 @@ PyTypeObject PyType_Type = {
47984821
0, /* tp_hash */
47994822
(ternaryfunc)type_call, /* tp_call */
48004823
0, /* tp_str */
4801-
(getattrofunc)type_getattro, /* tp_getattro */
4824+
(getattrofunc)_Py_type_getattro, /* tp_getattro */
48024825
(setattrofunc)type_setattro, /* tp_setattro */
48034826
0, /* tp_as_buffer */
48044827
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |

0 commit comments

Comments
 (0)