Skip to content

Commit 44a4628

Browse files
[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 3e245fa) Co-authored-by: Vyron Vasileiadis <hi@fedonman.com>
1 parent 87c9dfb commit 44a4628

4 files changed

Lines changed: 36 additions & 3 deletions

File tree

Lib/_py_abc.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ def _abc_caches_clear(cls):
9292
def __instancecheck__(cls, instance):
9393
"""Override for isinstance(instance, cls)."""
9494
# Inline the cache checking
95-
subclass = instance.__class__
95+
try:
96+
subclass = instance.__class__
97+
except AttributeError:
98+
# Fall back to the type when the instance has no __class__,
99+
# matching the behaviour of the built-in isinstance() (gh-153772).
100+
subclass = type(instance)
96101
if subclass in cls._abc_cache:
97102
return True
98103
subtype = type(instance)

Lib/test/test_abc.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,25 @@ class C(str): pass
349349
self.assertTrue(issubclass(C, A))
350350
self.assertTrue(issubclass(C, (A,)))
351351

352+
def test_instancecheck_no_class(self):
353+
# gh-153772: __instancecheck__ must fall back to type(instance)
354+
# when the instance has no __class__, matching isinstance().
355+
class NoClass:
356+
def __getattribute__(self, name):
357+
if name == "__class__":
358+
raise AttributeError(name)
359+
return super().__getattribute__(name)
360+
361+
class A(metaclass=abc_ABCMeta):
362+
pass
363+
364+
obj = NoClass()
365+
# Must return False rather than propagating the AttributeError.
366+
self.assertNotIsInstance(obj, A)
367+
# Registering the actual type makes the fallback report a match.
368+
A.register(NoClass)
369+
self.assertIsInstance(obj, A)
370+
352371
def test_registration_edge_cases(self):
353372
class A(metaclass=abc_ABCMeta):
354373
pass
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:func:`isinstance` checks against :mod:`collections.abc` classes such as
2+
:class:`~collections.abc.Mapping` no longer raise :exc:`AttributeError`
3+
when the instance has no ``__class__``. The abstract base class machinery
4+
now falls back to the object's type in that case, matching the behaviour of
5+
the built-in :func:`isinstance`.

Modules/_abc.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -622,11 +622,15 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
622622
return NULL;
623623
}
624624

625-
subclass = PyObject_GetAttr(instance, &_Py_ID(__class__));
626-
if (subclass == NULL) {
625+
if (PyObject_GetOptionalAttr(instance, &_Py_ID(__class__), &subclass) < 0) {
627626
Py_DECREF(impl);
628627
return NULL;
629628
}
629+
if (subclass == NULL) {
630+
/* Fall back to the type when the instance has no __class__, matching
631+
the behaviour of the built-in isinstance() (gh-153772). */
632+
subclass = Py_NewRef((PyObject *)Py_TYPE(instance));
633+
}
630634
/* Inline the cache checking. */
631635
int incache = _in_weak_set(impl, &impl->_abc_cache, subclass);
632636
if (incache < 0) {

0 commit comments

Comments
 (0)