diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py index c870ae9048b4f1..291d4bfa756f8d 100644 --- a/Lib/_py_abc.py +++ b/Lib/_py_abc.py @@ -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() @@ -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. @@ -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: @@ -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 diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 5ce57cc209ea85..439cbb56339962 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -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, diff --git a/Misc/NEWS.d/next/Library/2024-05-29-10-32-59.gh-issue-116093.Gcb6Md.rst b/Misc/NEWS.d/next/Library/2024-05-29-10-32-59.gh-issue-116093.Gcb6Md.rst new file mode 100644 index 00000000000000..73da45a5984b6a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-05-29-10-32-59.gh-issue-116093.Gcb6Md.rst @@ -0,0 +1,2 @@ +Fixed an issue with :meth:`~class.__subclasscheck__` where it can be called within :meth:`~object.__init_subclass__` before the subclass is initialized with the necessary ABC cache. +Patch by Ben Hsing diff --git a/Modules/_abc.c b/Modules/_abc.c index 4f4b24b035db4a..fc00de68001d55 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -621,6 +621,11 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self, if (impl == NULL) { return NULL; } + PyObject *dict = _PyType_GetDict((PyTypeObject *)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) { @@ -715,6 +720,11 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self, if (impl == NULL) { return NULL; } + PyObject *dict = _PyType_GetDict((PyTypeObject *)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);