Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Lib/_py_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ class ABCMeta(type):
# external code.
_abc_invalidation_counter = 0

def __new__(mcls, name, bases, namespace, /, **kwargs):
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
def __init__(cls, name, bases, namespace, /, **kwargs):
# Compute set of abstract method names
abstracts = {name
for name, value in namespace.items()
Expand All @@ -49,7 +48,6 @@ def __new__(mcls, name, bases, namespace, /, **kwargs):
cls._abc_cache = WeakSet()
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
return cls

def register(cls, subclass):
"""Register a virtual subclass of an ABC.
Expand Down Expand Up @@ -91,6 +89,8 @@ def _abc_caches_clear(cls):

def __instancecheck__(cls, instance):
"""Override for isinstance(instance, cls)."""
if '_abc_cache' not in cls.__dict__:
cls.__class__.__init__(cls, cls.__name__, cls.__bases__, cls.__dict__)
# Inline the cache checking
subclass = instance.__class__
if subclass in cls._abc_cache:
Expand All @@ -107,6 +107,8 @@ def __instancecheck__(cls, instance):

def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
if '_abc_cache' not in cls.__dict__:
cls.__class__.__init__(cls, cls.__name__, cls.__bases__, cls.__dict__)
if not isinstance(subclass, type):
raise TypeError('issubclass() arg 1 must be a class')
# Check cache
Expand Down
56 changes: 56 additions & 0 deletions Lib/test/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,62 @@ class B(A, metaclass=abc_ABCMeta, name="test"):
pass
self.assertEqual(saved_kwargs, dict(name="test"))


def test_subclasscheck_in_init_subclass1(self):
# test for gh-82266
class A(metaclass=abc_ABCMeta):
pass

class B(metaclass=abc_ABCMeta):
def __init_subclass__(cls):
assert not issubclass(cls, A)

class C(A):
pass

try:
class AB(A, B):
pass
except Exception:
pass

self.assertTrue(issubclass(C, A))
self.assertFalse(issubclass(C, B))

def test_subclasscheck_in_init_subclass2(self):
# test for gh-116093
class A(metaclass=abc_ABCMeta):
pass

class B(metaclass=abc_ABCMeta):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__()
issubclass(A, A)
issubclass(A, B)

class AB(A, B):
pass

self.assertFalse(issubclass(A, B))

def test_subclasscheck_in_init_subclass3(self):
# test for gh-119699
class A(metaclass=abc_ABCMeta):
pass

class B(A):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__()
issubclass(B, C)

class C(A):
pass

class BC(B, C):
pass

self.assertTrue(issubclass(B, B))

return TestLegacyAPI, TestABC, TestABCWithInitSubclass

TestLegacyAPI_Py, TestABC_Py, TestABCWithInitSubclass_Py = test_factory(abc.ABCMeta,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed an issue with ``ABCMeta.__subclasscheck__`` where it can be called within ``__init_subclass__`` before the subclass is initialized with the necessary ABC cache.
10 changes: 10 additions & 0 deletions Modules/_abc.c
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,11 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
if (impl == NULL) {
return NULL;
}
PyObject *dict = _PyType_GetDict(self);
if (!PyDict_Contains(dict, &_Py_ID(_abc_impl))) {
_abc__abc_init(module, self);
impl = _get_impl(module, self);
}

subclass = PyObject_GetAttr(instance, &_Py_ID(__class__));
if (subclass == NULL) {
Expand Down Expand Up @@ -715,6 +720,11 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
if (impl == NULL) {
return NULL;
}
PyObject *dict = _PyType_GetDict(self);
if (!PyDict_Contains(dict, &_Py_ID(_abc_impl))) {
_abc__abc_init(module, self);
impl = _get_impl(module, self);
}

/* 1. Check cache. */
incache = _in_weak_set(impl, &impl->_abc_cache, subclass);
Expand Down