From 23f9f6b08f66004f598eeb059ea499ce8fa462ce Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 16 Oct 2026 20:01:16 +0200 Subject: [PATCH 1/3] Skip the __class__ lookup in isinstance() when it cannot differ from the type --- Objects/abstract.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Objects/abstract.c b/Objects/abstract.c index da2a0b0c285b52..2cd68041c38c2b 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2600,6 +2600,28 @@ check_class(PyObject *cls, const char *error) return -1; } +/* Return 1 if inst.__class__ is known to evaluate to Py_TYPE(inst) without + running any code: generic attribute lookup resolves __class__ to object's + own getset descriptor. That descriptor is a data descriptor, so it wins + over the instance __dict__, and object_get_class() just returns the type. + Types with a custom tp_getattro (modules, type objects, classes with + __getattr__/__getattribute__) or a __class__ override fall back to the + generic path. */ +static inline int +class_attr_is_type(PyObject *inst) +{ + PyTypeObject *tp = Py_TYPE(inst); + if (tp->tp_getattro != PyObject_GenericGetAttr) { + return 0; + } + PyObject *descr = _PyType_LookupRef(tp, &_Py_ID(__class__)); + int res = (descr != NULL + && Py_IS_TYPE(descr, &PyGetSetDescr_Type) + && PyDescr_TYPE(descr) == &PyBaseObject_Type); + Py_XDECREF(descr); + return res; +} + static int object_isinstance(PyObject *inst, PyObject *cls) { @@ -2608,6 +2630,10 @@ object_isinstance(PyObject *inst, PyObject *cls) if (PyType_Check(cls)) { retval = PyObject_TypeCheck(inst, (PyTypeObject *)cls); if (retval == 0) { + if (class_attr_is_type(inst)) { + /* __class__ is Py_TYPE(inst): nothing more to check. */ + return 0; + } retval = PyObject_GetOptionalAttr(inst, &_Py_ID(__class__), &icls); if (icls != NULL) { if (icls != (PyObject *)(Py_TYPE(inst)) && PyType_Check(icls)) { From 05f46699b48f4f22b180712028acbce37f7d7c29 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 16 Oct 2026 20:50:17 +0200 Subject: [PATCH 2/3] Use a stack reference for the __class__ descriptor lookup in isinstance() --- Objects/abstract.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index 2cd68041c38c2b..2599987ac45ebf 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2600,13 +2600,9 @@ check_class(PyObject *cls, const char *error) return -1; } -/* Return 1 if inst.__class__ is known to evaluate to Py_TYPE(inst) without - running any code: generic attribute lookup resolves __class__ to object's - own getset descriptor. That descriptor is a data descriptor, so it wins - over the instance __dict__, and object_get_class() just returns the type. - Types with a custom tp_getattro (modules, type objects, classes with - __getattr__/__getattribute__) or a __class__ override fall back to the - generic path. */ +/* Return 1 if inst.__class__ would resolve to object's own getset descriptor, + i.e. evaluate to Py_TYPE(inst). Types with a custom tp_getattro or an + overridden __class__ return 0 and take the generic lookup. */ static inline int class_attr_is_type(PyObject *inst) { @@ -2614,11 +2610,15 @@ class_attr_is_type(PyObject *inst) if (tp->tp_getattro != PyObject_GenericGetAttr) { return 0; } - PyObject *descr = _PyType_LookupRef(tp, &_Py_ID(__class__)); - int res = (descr != NULL - && Py_IS_TYPE(descr, &PyGetSetDescr_Type) + _PyStackRef cref; + _PyType_LookupStackRefAndVersion(tp, &_Py_ID(__class__), &cref); + if (PyStackRef_IsNull(cref)) { + return 0; + } + PyObject *descr = PyStackRef_AsPyObjectBorrow(cref); + int res = (Py_IS_TYPE(descr, &PyGetSetDescr_Type) && PyDescr_TYPE(descr) == &PyBaseObject_Type); - Py_XDECREF(descr); + PyStackRef_CLOSE(cref); return res; } @@ -2631,7 +2631,6 @@ object_isinstance(PyObject *inst, PyObject *cls) retval = PyObject_TypeCheck(inst, (PyTypeObject *)cls); if (retval == 0) { if (class_attr_is_type(inst)) { - /* __class__ is Py_TYPE(inst): nothing more to check. */ return 0; } retval = PyObject_GetOptionalAttr(inst, &_Py_ID(__class__), &icls); From 6fad9d772487baecd558199900e0100aeb07aba7 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 16 Oct 2026 23:06:22 +0200 Subject: [PATCH 3/3] Document that isinstance() also consults a differing __class__ attribute --- Doc/builtins/functions.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/builtins/functions.rst b/Doc/builtins/functions.rst index f6203b026d32a4..e7c88e5d0da778 100644 --- a/Doc/builtins/functions.rst +++ b/Doc/builtins/functions.rst @@ -1174,6 +1174,11 @@ are always available. They are listed here in alphabetical order. a :exc:`TypeError` exception is raised. :exc:`TypeError` may not be raised for an invalid type if an earlier check succeeds. + If *object* has a ``__class__`` attribute that differs from + :func:`type(object) ` (for example a proxy, or a :mod:`unittest.mock` + object created with a *spec*), ``True`` is also returned when that class + is a subclass of *classinfo*. + .. versionchanged:: 3.10 *classinfo* can be a :ref:`types-union`.