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]