From e3cd77e7f06a9d8bd01a922934afeb6314a0a595 Mon Sep 17 00:00:00 2001 From: hongweipeng Date: Wed, 15 Dec 2021 00:08:32 +0800 Subject: [PATCH 1/4] Fix throw exception in __init__subclass__ causes wrong isinstance() and issubclass() results in subclasses of abc.ABCMeta. --- Lib/_py_abc.py | 92 ++++++++++++------- Lib/test/test_abc.py | 24 +++++ .../2021-12-15-15-51-32.bpo-38085.FnIBaB.rst | 3 + Modules/_abc.c | 19 ++++ 4 files changed, 104 insertions(+), 34 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py index c870ae9048b4f1..af5c14964d8933 100644 --- a/Lib/_py_abc.py +++ b/Lib/_py_abc.py @@ -11,6 +11,39 @@ def get_cache_token(): return ABCMeta._abc_invalidation_counter +class _abc_data(object): + def __init__(self): + self._abc_registry = WeakSet() + self._abc_cache = WeakSet() + self._abc_negative_cache = WeakSet() + self._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + + +def _compute_abstract_methods(cls): + """Compute set of abstract method names""" + abstracts = {name + for name, value in cls.__dict__.items() + if getattr(value, "__isabstractmethod__", False)} + for base in cls.__bases__: + for name in getattr(base, "__abstractmethods__", set()): + value = getattr(cls, name, None) + if getattr(value, "__isabstractmethod__", False): + abstracts.add(name) + cls.__abstractmethods__ = frozenset(abstracts) + + +def _abc_init(cls): + _compute_abstract_methods(cls) + # Set up inheritance registry + cls._abc_impl = _abc_data() + + +def _get_impl(cls): + if '_abc_impl' not in cls.__dict__: + _abc_init(cls) + return cls._abc_impl + + class ABCMeta(type): """Metaclass for defining Abstract Base Classes (ABCs). @@ -34,21 +67,7 @@ class ABCMeta(type): def __new__(mcls, name, bases, namespace, /, **kwargs): cls = super().__new__(mcls, name, bases, namespace, **kwargs) - # Compute set of abstract method names - abstracts = {name - for name, value in namespace.items() - if getattr(value, "__isabstractmethod__", False)} - for base in bases: - for name in getattr(base, "__abstractmethods__", set()): - value = getattr(cls, name, None) - if getattr(value, "__isabstractmethod__", False): - abstracts.add(name) - cls.__abstractmethods__ = frozenset(abstracts) - # Set up inheritance registry - cls._abc_registry = WeakSet() - cls._abc_cache = WeakSet() - cls._abc_negative_cache = WeakSet() - cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + _abc_init(cls) return cls def register(cls, subclass): @@ -65,7 +84,8 @@ def register(cls, subclass): if issubclass(cls, subclass): # This would create a cycle, which is bad for the algorithm below raise RuntimeError("Refusing to create an inheritance cycle") - cls._abc_registry.add(subclass) + abc_impl = _get_impl(cls) + abc_impl._abc_registry.add(subclass) ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache return subclass @@ -82,24 +102,27 @@ def _dump_registry(cls, file=None): def _abc_registry_clear(cls): """Clear the registry (for debugging or testing).""" - cls._abc_registry.clear() + abc_impl = _get_impl(cls) + abc_impl._abc_registry.clear() def _abc_caches_clear(cls): """Clear the caches (for debugging or testing).""" - cls._abc_cache.clear() - cls._abc_negative_cache.clear() + abc_impl = _get_impl(cls) + abc_impl._abc_cache.clear() + abc_impl._abc_negative_cache.clear() def __instancecheck__(cls, instance): """Override for isinstance(instance, cls).""" # Inline the cache checking subclass = instance.__class__ - if subclass in cls._abc_cache: + abc_impl = _get_impl(cls) + if subclass in abc_impl._abc_cache: return True subtype = type(instance) if subtype is subclass: - if (cls._abc_negative_cache_version == + if (abc_impl._abc_negative_cache_version == ABCMeta._abc_invalidation_counter and - subclass in cls._abc_negative_cache): + subclass in abc_impl._abc_negative_cache): return False # Fall back to the subclass check. return cls.__subclasscheck__(subclass) @@ -109,39 +132,40 @@ def __subclasscheck__(cls, subclass): """Override for issubclass(subclass, cls).""" if not isinstance(subclass, type): raise TypeError('issubclass() arg 1 must be a class') + abc_impl = _get_impl(cls) # Check cache - if subclass in cls._abc_cache: + if subclass in abc_impl._abc_cache: return True # Check negative cache; may have to invalidate - if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: + if abc_impl._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: # Invalidate the negative cache - cls._abc_negative_cache = WeakSet() - cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter - elif subclass in cls._abc_negative_cache: + abc_impl._abc_negative_cache = WeakSet() + abc_impl._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + elif subclass in abc_impl._abc_negative_cache: return False # Check the subclass hook ok = cls.__subclasshook__(subclass) if ok is not NotImplemented: assert isinstance(ok, bool) if ok: - cls._abc_cache.add(subclass) + abc_impl._abc_cache.add(subclass) else: - cls._abc_negative_cache.add(subclass) + abc_impl._abc_negative_cache.add(subclass) return ok # Check if it's a direct subclass if cls in getattr(subclass, '__mro__', ()): - cls._abc_cache.add(subclass) + abc_impl._abc_cache.add(subclass) return True # Check if it's a subclass of a registered class (recursive) - for rcls in cls._abc_registry: + for rcls in abc_impl._abc_registry: if issubclass(subclass, rcls): - cls._abc_cache.add(subclass) + abc_impl._abc_cache.add(subclass) return True # Check if it's a subclass of a subclass (recursive) for scls in cls.__subclasses__(): if issubclass(subclass, scls): - cls._abc_cache.add(subclass) + abc_impl._abc_cache.add(subclass) return True # No dice; update negative cache - cls._abc_negative_cache.add(subclass) + abc_impl._abc_negative_cache.add(subclass) return False diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index c1d750dba83f99..a5ececf9a8855b 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -521,6 +521,30 @@ def foo(self): self.assertEqual(A.__abstractmethods__, set()) A() + def test_init_subclass_exception(self): + class Animal(metaclass=abc_ABCMeta): + pass + + class Plant(metaclass=abc_ABCMeta): + def __init_subclass__(cls): + super().__init_subclass__() + assert not issubclass(cls, Animal), "Plants cannot be Animals" + + class Dog(Animal): + pass + + try: + class Triffid(Animal, Plant): + pass + except Exception: + pass + + d = Dog() + self.assertTrue(issubclass(Dog, Animal)) + self.assertFalse(issubclass(Dog, Plant)) + self.assertTrue(isinstance(d, Animal)) + self.assertFalse(isinstance(d, Plant)) + def test_update_new_abstractmethods(self): class A(metaclass=abc_ABCMeta): diff --git a/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst b/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst new file mode 100644 index 00000000000000..cfe5fd835bfd3b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst @@ -0,0 +1,3 @@ +Fix the incorrect :func:`isinstance` and :func:`issubclass` results when +throwing exceptions in ``__init_subclass__`` in subclasses of +:class:`abc.ABCMeta`. Patch by Weipeng Hong. diff --git a/Modules/_abc.c b/Modules/_abc.c index b7465c379dddf4..de56a2a390e46e 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -117,9 +117,28 @@ static PyType_Spec _abc_data_type_spec = { .slots = _abc_data_type_spec_slots, }; +/* Forward declaration. */ +static PyObject * _abc__abc_init(PyObject *module, PyObject *self); + +static int +_already_abc_init(PyObject *self) { + PyObject *oname = _PyUnicode_FromId(&PyId__abc_impl); + if (!oname) { + return 0; + } + PyObject *dict = _PyObject_GetAttrId(self, &PyId___dict__); + int res = PyMapping_HasKey(dict, oname); + Py_DECREF(dict); + return res; +} + static _abc_data * _get_impl(PyObject *module, PyObject *self) { + if (!_already_abc_init(self)) { + _abc__abc_init(module, self); + } + _abcmodule_state *state = get_abc_state(module); PyObject *impl = _PyObject_GetAttrId(self, &PyId__abc_impl); if (impl == NULL) { From ec2946c8f21f6841b4df90e9fbd8ef4ffa7caad5 Mon Sep 17 00:00:00 2001 From: hongweipeng Date: Fri, 24 Dec 2021 10:08:44 +0800 Subject: [PATCH 2/4] remove subclasse when __init_subclass__ fail --- Lib/_py_abc.py | 92 +++++++------------ .../2021-12-24-10-05-32.bpo-1332.Brbs6A.rst | 2 + .../2021-12-15-15-51-32.bpo-38085.FnIBaB.rst | 3 - Modules/_abc.c | 19 ---- Objects/typeobject.c | 1 + 5 files changed, 37 insertions(+), 80 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-12-24-10-05-32.bpo-1332.Brbs6A.rst delete mode 100644 Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py index af5c14964d8933..c870ae9048b4f1 100644 --- a/Lib/_py_abc.py +++ b/Lib/_py_abc.py @@ -11,39 +11,6 @@ def get_cache_token(): return ABCMeta._abc_invalidation_counter -class _abc_data(object): - def __init__(self): - self._abc_registry = WeakSet() - self._abc_cache = WeakSet() - self._abc_negative_cache = WeakSet() - self._abc_negative_cache_version = ABCMeta._abc_invalidation_counter - - -def _compute_abstract_methods(cls): - """Compute set of abstract method names""" - abstracts = {name - for name, value in cls.__dict__.items() - if getattr(value, "__isabstractmethod__", False)} - for base in cls.__bases__: - for name in getattr(base, "__abstractmethods__", set()): - value = getattr(cls, name, None) - if getattr(value, "__isabstractmethod__", False): - abstracts.add(name) - cls.__abstractmethods__ = frozenset(abstracts) - - -def _abc_init(cls): - _compute_abstract_methods(cls) - # Set up inheritance registry - cls._abc_impl = _abc_data() - - -def _get_impl(cls): - if '_abc_impl' not in cls.__dict__: - _abc_init(cls) - return cls._abc_impl - - class ABCMeta(type): """Metaclass for defining Abstract Base Classes (ABCs). @@ -67,7 +34,21 @@ class ABCMeta(type): def __new__(mcls, name, bases, namespace, /, **kwargs): cls = super().__new__(mcls, name, bases, namespace, **kwargs) - _abc_init(cls) + # Compute set of abstract method names + abstracts = {name + for name, value in namespace.items() + if getattr(value, "__isabstractmethod__", False)} + for base in bases: + for name in getattr(base, "__abstractmethods__", set()): + value = getattr(cls, name, None) + if getattr(value, "__isabstractmethod__", False): + abstracts.add(name) + cls.__abstractmethods__ = frozenset(abstracts) + # Set up inheritance registry + cls._abc_registry = WeakSet() + cls._abc_cache = WeakSet() + cls._abc_negative_cache = WeakSet() + cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter return cls def register(cls, subclass): @@ -84,8 +65,7 @@ def register(cls, subclass): if issubclass(cls, subclass): # This would create a cycle, which is bad for the algorithm below raise RuntimeError("Refusing to create an inheritance cycle") - abc_impl = _get_impl(cls) - abc_impl._abc_registry.add(subclass) + cls._abc_registry.add(subclass) ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache return subclass @@ -102,27 +82,24 @@ def _dump_registry(cls, file=None): def _abc_registry_clear(cls): """Clear the registry (for debugging or testing).""" - abc_impl = _get_impl(cls) - abc_impl._abc_registry.clear() + cls._abc_registry.clear() def _abc_caches_clear(cls): """Clear the caches (for debugging or testing).""" - abc_impl = _get_impl(cls) - abc_impl._abc_cache.clear() - abc_impl._abc_negative_cache.clear() + cls._abc_cache.clear() + cls._abc_negative_cache.clear() def __instancecheck__(cls, instance): """Override for isinstance(instance, cls).""" # Inline the cache checking subclass = instance.__class__ - abc_impl = _get_impl(cls) - if subclass in abc_impl._abc_cache: + if subclass in cls._abc_cache: return True subtype = type(instance) if subtype is subclass: - if (abc_impl._abc_negative_cache_version == + if (cls._abc_negative_cache_version == ABCMeta._abc_invalidation_counter and - subclass in abc_impl._abc_negative_cache): + subclass in cls._abc_negative_cache): return False # Fall back to the subclass check. return cls.__subclasscheck__(subclass) @@ -132,40 +109,39 @@ def __subclasscheck__(cls, subclass): """Override for issubclass(subclass, cls).""" if not isinstance(subclass, type): raise TypeError('issubclass() arg 1 must be a class') - abc_impl = _get_impl(cls) # Check cache - if subclass in abc_impl._abc_cache: + if subclass in cls._abc_cache: return True # Check negative cache; may have to invalidate - if abc_impl._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: + if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: # Invalidate the negative cache - abc_impl._abc_negative_cache = WeakSet() - abc_impl._abc_negative_cache_version = ABCMeta._abc_invalidation_counter - elif subclass in abc_impl._abc_negative_cache: + cls._abc_negative_cache = WeakSet() + cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + elif subclass in cls._abc_negative_cache: return False # Check the subclass hook ok = cls.__subclasshook__(subclass) if ok is not NotImplemented: assert isinstance(ok, bool) if ok: - abc_impl._abc_cache.add(subclass) + cls._abc_cache.add(subclass) else: - abc_impl._abc_negative_cache.add(subclass) + cls._abc_negative_cache.add(subclass) return ok # Check if it's a direct subclass if cls in getattr(subclass, '__mro__', ()): - abc_impl._abc_cache.add(subclass) + cls._abc_cache.add(subclass) return True # Check if it's a subclass of a registered class (recursive) - for rcls in abc_impl._abc_registry: + for rcls in cls._abc_registry: if issubclass(subclass, rcls): - abc_impl._abc_cache.add(subclass) + cls._abc_cache.add(subclass) return True # Check if it's a subclass of a subclass (recursive) for scls in cls.__subclasses__(): if issubclass(subclass, scls): - abc_impl._abc_cache.add(subclass) + cls._abc_cache.add(subclass) return True # No dice; update negative cache - abc_impl._abc_negative_cache.add(subclass) + cls._abc_negative_cache.add(subclass) return False diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-12-24-10-05-32.bpo-1332.Brbs6A.rst b/Misc/NEWS.d/next/Core and Builtins/2021-12-24-10-05-32.bpo-1332.Brbs6A.rst new file mode 100644 index 00000000000000..6d8cf8df0b0e05 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-12-24-10-05-32.bpo-1332.Brbs6A.rst @@ -0,0 +1,2 @@ +When an error occurs in ``__init_subclass__``, the subclass is removed from +the ``__subclasses__`` of the base class. Patch by Weipeng Hong. diff --git a/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst b/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst deleted file mode 100644 index cfe5fd835bfd3b..00000000000000 --- a/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix the incorrect :func:`isinstance` and :func:`issubclass` results when -throwing exceptions in ``__init_subclass__`` in subclasses of -:class:`abc.ABCMeta`. Patch by Weipeng Hong. diff --git a/Modules/_abc.c b/Modules/_abc.c index de56a2a390e46e..b7465c379dddf4 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -117,28 +117,9 @@ static PyType_Spec _abc_data_type_spec = { .slots = _abc_data_type_spec_slots, }; -/* Forward declaration. */ -static PyObject * _abc__abc_init(PyObject *module, PyObject *self); - -static int -_already_abc_init(PyObject *self) { - PyObject *oname = _PyUnicode_FromId(&PyId__abc_impl); - if (!oname) { - return 0; - } - PyObject *dict = _PyObject_GetAttrId(self, &PyId___dict__); - int res = PyMapping_HasKey(dict, oname); - Py_DECREF(dict); - return res; -} - static _abc_data * _get_impl(PyObject *module, PyObject *self) { - if (!_already_abc_init(self)) { - _abc__abc_init(module, self); - } - _abcmodule_state *state = get_abc_state(module); PyObject *impl = _PyObject_GetAttrId(self, &PyId__abc_impl); if (impl == NULL) { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 2fd93b61c0b2b0..490a0a4452739f 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -8567,6 +8567,7 @@ type_new_init_subclass(PyTypeObject *type, PyObject *kwds) PyObject *result = PyObject_VectorcallDict(func, NULL, 0, kwds); Py_DECREF(func); if (result == NULL) { + remove_all_subclasses(type, type->tp_bases); return -1; } From 8415670127a951e2e59e27eb4a4ed7d93a4c42cd Mon Sep 17 00:00:00 2001 From: hongweipeng Date: Fri, 24 Dec 2021 10:10:53 +0800 Subject: [PATCH 3/4] add test --- Lib/test/test_builtin.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 6dc4fa555021cc..e9367f353b9369 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2334,6 +2334,20 @@ class B: with self.assertRaises(TypeError): type('A', (B,), {'__slots__': '__weakref__'}) + def test_type_new_create_subclass_fail(self): + class A(object): + def __init_subclass__(cls, **kwargs): + if cls.__name__ == 'C': + raise Exception('1') + + class B(A): pass # create success + a_subclasses = A.__subclasses__() + try: + class C(A): pass # create fail + except: + pass + self.assertEqual(a_subclasses, A.__subclasses__()) + def test_namespace_order(self): # bpo-34320: namespace should preserve order od = collections.OrderedDict([('a', 1), ('b', 2)]) From c98ad6d3827e3764f2d5c8932cb4e38a910a953a Mon Sep 17 00:00:00 2001 From: hongweipeng Date: Fri, 24 Dec 2021 14:02:25 +0800 Subject: [PATCH 4/4] Revert --- Lib/_py_abc.py | 92 ++++++++++++------- Lib/test/test_builtin.py | 14 --- .../2021-12-24-10-05-32.bpo-1332.Brbs6A.rst | 2 - .../2021-12-15-15-51-32.bpo-38085.FnIBaB.rst | 3 + Modules/_abc.c | 19 ++++ Objects/typeobject.c | 1 - 6 files changed, 80 insertions(+), 51 deletions(-) delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-12-24-10-05-32.bpo-1332.Brbs6A.rst create mode 100644 Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py index c870ae9048b4f1..af5c14964d8933 100644 --- a/Lib/_py_abc.py +++ b/Lib/_py_abc.py @@ -11,6 +11,39 @@ def get_cache_token(): return ABCMeta._abc_invalidation_counter +class _abc_data(object): + def __init__(self): + self._abc_registry = WeakSet() + self._abc_cache = WeakSet() + self._abc_negative_cache = WeakSet() + self._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + + +def _compute_abstract_methods(cls): + """Compute set of abstract method names""" + abstracts = {name + for name, value in cls.__dict__.items() + if getattr(value, "__isabstractmethod__", False)} + for base in cls.__bases__: + for name in getattr(base, "__abstractmethods__", set()): + value = getattr(cls, name, None) + if getattr(value, "__isabstractmethod__", False): + abstracts.add(name) + cls.__abstractmethods__ = frozenset(abstracts) + + +def _abc_init(cls): + _compute_abstract_methods(cls) + # Set up inheritance registry + cls._abc_impl = _abc_data() + + +def _get_impl(cls): + if '_abc_impl' not in cls.__dict__: + _abc_init(cls) + return cls._abc_impl + + class ABCMeta(type): """Metaclass for defining Abstract Base Classes (ABCs). @@ -34,21 +67,7 @@ class ABCMeta(type): def __new__(mcls, name, bases, namespace, /, **kwargs): cls = super().__new__(mcls, name, bases, namespace, **kwargs) - # Compute set of abstract method names - abstracts = {name - for name, value in namespace.items() - if getattr(value, "__isabstractmethod__", False)} - for base in bases: - for name in getattr(base, "__abstractmethods__", set()): - value = getattr(cls, name, None) - if getattr(value, "__isabstractmethod__", False): - abstracts.add(name) - cls.__abstractmethods__ = frozenset(abstracts) - # Set up inheritance registry - cls._abc_registry = WeakSet() - cls._abc_cache = WeakSet() - cls._abc_negative_cache = WeakSet() - cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + _abc_init(cls) return cls def register(cls, subclass): @@ -65,7 +84,8 @@ def register(cls, subclass): if issubclass(cls, subclass): # This would create a cycle, which is bad for the algorithm below raise RuntimeError("Refusing to create an inheritance cycle") - cls._abc_registry.add(subclass) + abc_impl = _get_impl(cls) + abc_impl._abc_registry.add(subclass) ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache return subclass @@ -82,24 +102,27 @@ def _dump_registry(cls, file=None): def _abc_registry_clear(cls): """Clear the registry (for debugging or testing).""" - cls._abc_registry.clear() + abc_impl = _get_impl(cls) + abc_impl._abc_registry.clear() def _abc_caches_clear(cls): """Clear the caches (for debugging or testing).""" - cls._abc_cache.clear() - cls._abc_negative_cache.clear() + abc_impl = _get_impl(cls) + abc_impl._abc_cache.clear() + abc_impl._abc_negative_cache.clear() def __instancecheck__(cls, instance): """Override for isinstance(instance, cls).""" # Inline the cache checking subclass = instance.__class__ - if subclass in cls._abc_cache: + abc_impl = _get_impl(cls) + if subclass in abc_impl._abc_cache: return True subtype = type(instance) if subtype is subclass: - if (cls._abc_negative_cache_version == + if (abc_impl._abc_negative_cache_version == ABCMeta._abc_invalidation_counter and - subclass in cls._abc_negative_cache): + subclass in abc_impl._abc_negative_cache): return False # Fall back to the subclass check. return cls.__subclasscheck__(subclass) @@ -109,39 +132,40 @@ def __subclasscheck__(cls, subclass): """Override for issubclass(subclass, cls).""" if not isinstance(subclass, type): raise TypeError('issubclass() arg 1 must be a class') + abc_impl = _get_impl(cls) # Check cache - if subclass in cls._abc_cache: + if subclass in abc_impl._abc_cache: return True # Check negative cache; may have to invalidate - if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: + if abc_impl._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: # Invalidate the negative cache - cls._abc_negative_cache = WeakSet() - cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter - elif subclass in cls._abc_negative_cache: + abc_impl._abc_negative_cache = WeakSet() + abc_impl._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + elif subclass in abc_impl._abc_negative_cache: return False # Check the subclass hook ok = cls.__subclasshook__(subclass) if ok is not NotImplemented: assert isinstance(ok, bool) if ok: - cls._abc_cache.add(subclass) + abc_impl._abc_cache.add(subclass) else: - cls._abc_negative_cache.add(subclass) + abc_impl._abc_negative_cache.add(subclass) return ok # Check if it's a direct subclass if cls in getattr(subclass, '__mro__', ()): - cls._abc_cache.add(subclass) + abc_impl._abc_cache.add(subclass) return True # Check if it's a subclass of a registered class (recursive) - for rcls in cls._abc_registry: + for rcls in abc_impl._abc_registry: if issubclass(subclass, rcls): - cls._abc_cache.add(subclass) + abc_impl._abc_cache.add(subclass) return True # Check if it's a subclass of a subclass (recursive) for scls in cls.__subclasses__(): if issubclass(subclass, scls): - cls._abc_cache.add(subclass) + abc_impl._abc_cache.add(subclass) return True # No dice; update negative cache - cls._abc_negative_cache.add(subclass) + abc_impl._abc_negative_cache.add(subclass) return False diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index e9367f353b9369..6dc4fa555021cc 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2334,20 +2334,6 @@ class B: with self.assertRaises(TypeError): type('A', (B,), {'__slots__': '__weakref__'}) - def test_type_new_create_subclass_fail(self): - class A(object): - def __init_subclass__(cls, **kwargs): - if cls.__name__ == 'C': - raise Exception('1') - - class B(A): pass # create success - a_subclasses = A.__subclasses__() - try: - class C(A): pass # create fail - except: - pass - self.assertEqual(a_subclasses, A.__subclasses__()) - def test_namespace_order(self): # bpo-34320: namespace should preserve order od = collections.OrderedDict([('a', 1), ('b', 2)]) diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-12-24-10-05-32.bpo-1332.Brbs6A.rst b/Misc/NEWS.d/next/Core and Builtins/2021-12-24-10-05-32.bpo-1332.Brbs6A.rst deleted file mode 100644 index 6d8cf8df0b0e05..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2021-12-24-10-05-32.bpo-1332.Brbs6A.rst +++ /dev/null @@ -1,2 +0,0 @@ -When an error occurs in ``__init_subclass__``, the subclass is removed from -the ``__subclasses__`` of the base class. Patch by Weipeng Hong. diff --git a/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst b/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst new file mode 100644 index 00000000000000..cfe5fd835bfd3b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-12-15-15-51-32.bpo-38085.FnIBaB.rst @@ -0,0 +1,3 @@ +Fix the incorrect :func:`isinstance` and :func:`issubclass` results when +throwing exceptions in ``__init_subclass__`` in subclasses of +:class:`abc.ABCMeta`. Patch by Weipeng Hong. diff --git a/Modules/_abc.c b/Modules/_abc.c index b7465c379dddf4..de56a2a390e46e 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -117,9 +117,28 @@ static PyType_Spec _abc_data_type_spec = { .slots = _abc_data_type_spec_slots, }; +/* Forward declaration. */ +static PyObject * _abc__abc_init(PyObject *module, PyObject *self); + +static int +_already_abc_init(PyObject *self) { + PyObject *oname = _PyUnicode_FromId(&PyId__abc_impl); + if (!oname) { + return 0; + } + PyObject *dict = _PyObject_GetAttrId(self, &PyId___dict__); + int res = PyMapping_HasKey(dict, oname); + Py_DECREF(dict); + return res; +} + static _abc_data * _get_impl(PyObject *module, PyObject *self) { + if (!_already_abc_init(self)) { + _abc__abc_init(module, self); + } + _abcmodule_state *state = get_abc_state(module); PyObject *impl = _PyObject_GetAttrId(self, &PyId__abc_impl); if (impl == NULL) { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 490a0a4452739f..2fd93b61c0b2b0 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -8567,7 +8567,6 @@ type_new_init_subclass(PyTypeObject *type, PyObject *kwds) PyObject *result = PyObject_VectorcallDict(func, NULL, 0, kwds); Py_DECREF(func); if (result == NULL) { - remove_all_subclasses(type, type->tp_bases); return -1; } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy