https://github.com/python/cpython/commit/3e245faf34efc41c39bbe76071e6edb7a62692a8
commit: 3e245faf34efc41c39bbe76071e6edb7a62692a8
branch: main
author: Vyron Vasileiadis <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-08-28T20:08:53-07:00
summary:

gh-153772: Make abc isinstance() tolerate instances without __class__ (#154149)

The built-in isinstance() reads an instance's __class__ with a lookup that
suppresses AttributeError and falls back to the object's type, so
isinstance(obj, int) returns False for an object whose __class__ access
raises. ABCMeta.__instancecheck__ read __class__ directly instead, so
isinstance(obj, Mapping) leaked that AttributeError.

Fall back to type(instance) when __class__ is unavailable, in both the C
and the pure-Python implementations, so the abstract base classes behave
like the built-in isinstance(). Such objects are unusual, but they do turn
up in the wild (for example some Qt widgets).

files:
A Misc/NEWS.d/next/Library/2026-07-19-17-05-00.gh-issue-153772.cobjTK.rst
M Lib/_py_abc.py
M Lib/test/test_abc.py
M Modules/_abc.c

diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py
index c870ae9048b4f1..2bfb9489a7acdc 100644
--- a/Lib/_py_abc.py
+++ b/Lib/_py_abc.py
@@ -92,7 +92,12 @@ def _abc_caches_clear(cls):
     def __instancecheck__(cls, instance):
         """Override for isinstance(instance, cls)."""
         # Inline the cache checking
-        subclass = instance.__class__
+        try:
+            subclass = instance.__class__
+        except AttributeError:
+            # Fall back to the type when the instance has no __class__,
+            # matching the behaviour of the built-in isinstance() (gh-153772).
+            subclass = type(instance)
         if subclass in cls._abc_cache:
             return True
         subtype = type(instance)
diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py
index 59a45a2eda07b0..814d7fff2f4135 100644
--- a/Lib/test/test_abc.py
+++ b/Lib/test/test_abc.py
@@ -380,6 +380,25 @@ class C(str): pass
             self.assertIsSubclass(C, A)
             self.assertIsSubclass(C, (A,))
 
+        def test_instancecheck_no_class(self):
+            # gh-153772: __instancecheck__ must fall back to type(instance)
+            # when the instance has no __class__, matching isinstance().
+            class NoClass:
+                def __getattribute__(self, name):
+                    if name == "__class__":
+                        raise AttributeError(name)
+                    return super().__getattribute__(name)
+
+            class A(metaclass=abc_ABCMeta):
+                pass
+
+            obj = NoClass()
+            # Must return False rather than propagating the AttributeError.
+            self.assertNotIsInstance(obj, A)
+            # Registering the actual type makes the fallback report a match.
+            A.register(NoClass)
+            self.assertIsInstance(obj, A)
+
         def test_registration_edge_cases(self):
             class A(metaclass=abc_ABCMeta):
                 pass
diff --git 
a/Misc/NEWS.d/next/Library/2026-07-19-17-05-00.gh-issue-153772.cobjTK.rst 
b/Misc/NEWS.d/next/Library/2026-07-19-17-05-00.gh-issue-153772.cobjTK.rst
new file mode 100644
index 00000000000000..9393384151450d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-19-17-05-00.gh-issue-153772.cobjTK.rst
@@ -0,0 +1,5 @@
+:func:`isinstance` checks against :mod:`collections.abc` classes such as
+:class:`~collections.abc.Mapping` no longer raise :exc:`AttributeError`
+when the instance has no ``__class__``.  The abstract base class machinery
+now falls back to the object's type in that case, matching the behaviour of
+the built-in :func:`isinstance`.
diff --git a/Modules/_abc.c b/Modules/_abc.c
index 5826efbfecb690..bca9066d340ee4 100644
--- a/Modules/_abc.c
+++ b/Modules/_abc.c
@@ -629,11 +629,15 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject 
*self,
         return NULL;
     }
 
-    subclass = PyObject_GetAttr(instance, &_Py_ID(__class__));
-    if (subclass == NULL) {
+    if (PyObject_GetOptionalAttr(instance, &_Py_ID(__class__), &subclass) < 0) 
{
         Py_DECREF(impl);
         return NULL;
     }
+    if (subclass == NULL) {
+        /* Fall back to the type when the instance has no __class__, matching
+           the behaviour of the built-in isinstance() (gh-153772). */
+        subclass = Py_NewRef((PyObject *)Py_TYPE(instance));
+    }
     /* Inline the cache checking. */
     int incache = _in_weak_set(impl, &impl->_abc_cache, subclass);
     if (incache < 0) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to