https://github.com/python/cpython/commit/44a46281a500449b8e576cc824671326ba49d68c
commit: 44a46281a500449b8e576cc824671326ba49d68c
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-08-29T03:33:29Z
summary:

[3.13] gh-153772: Make abc isinstance() tolerate instances without __class__ 
(GH-154149) (#156543)

gh-153772: Make abc isinstance() tolerate instances without __class__ 
(GH-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).
(cherry picked from commit 3e245faf34efc41c39bbe76071e6edb7a62692a8)

Co-authored-by: Vyron Vasileiadis <[email protected]>

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 5ce57cc209ea85..72fd9671de345f 100644
--- a/Lib/test/test_abc.py
+++ b/Lib/test/test_abc.py
@@ -349,6 +349,25 @@ class C(str): pass
             self.assertTrue(issubclass(C, A))
             self.assertTrue(issubclass(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 baffb80ffe603f..7199fc494e12e7 100644
--- a/Modules/_abc.c
+++ b/Modules/_abc.c
@@ -622,11 +622,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