https://github.com/python/cpython/commit/d122f3e434efc81981bec25e0eff35a4a3a30764 commit: d122f3e434efc81981bec25e0eff35a4a3a30764 branch: 3.15 author: Miss Islington (bot) <[email protected]> committer: hugovk <[email protected]> date: 2026-08-29T10:10:18+03:00 summary:
[3.15] gh-153772: Make abc isinstance() tolerate instances without __class__ (GH-154149) (#156541) 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 80ee9e0ba56e75..bdfad263db6fc0 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -349,6 +349,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]
