This commit is contained in:
Maxim Martynov 2025-12-23 14:11:49 +05:30 committed by GitHub
commit 03bb6a0d20
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 484 additions and 110 deletions

View file

@ -413,6 +413,33 @@ arguments (:pep:`791`).
Improved modules
================
abc
---
* Reduce memory usage of :func:`issubclass` checks for :class:`abc.ABCMeta` subclasses.
:class:`abc.ABCMeta` subclasses can trigger downstream checks:
``issubclass(Some, Class)`` -> ``issubclass(Some, Parent)`` -> ``issubclass(Some, Top)``
(nothing found) -> ``issubclass(Some, Class.__subclasses__)`` -> ``issubclass(Some, SubClass)``
-> ``issubclass(Some, SubClass.__subclasses__)`` -> ...
Due to caching of ``issubclass`` result within each ABC class,
this could lead to memory bloat in large class trees, e.g. thousands of subclasses.
Now :meth:`!abc.ABCMeta.register` recursively calls ``Parent.register(subclass)``,
``Top.register(subclass)`` and so on for all base classes, so downstream checks are not needed.
Also :meth:`!abc.ABCMeta.__new__` checks if ``__subclasses__`` method is present within class,
and if not, disables recursive downstream checks in :func:`issubclass`
for specific abstract class and all its children.
This reduces both the number of checks, and the RAM usage by internal caches:
``issubclass(Some, Class)`` -> ``issubclass(Some, Parent)`` -> ``issubclass(Some, Top)``
(nothing found, stops here)
(Contributed by Maxim Martynov in :gh:`92810`.)
argparse
--------

View file

@ -49,6 +49,8 @@ extern "C" {
_Py_atomic_load_uint16_relaxed(&value)
#define FT_ATOMIC_LOAD_UINT32_RELAXED(value) \
_Py_atomic_load_uint32_relaxed(&value)
#define FT_ATOMIC_LOAD_UINT64_RELAXED(value) \
_Py_atomic_load_uint64_relaxed(&value)
#define FT_ATOMIC_LOAD_ULONG_RELAXED(value) \
_Py_atomic_load_ulong_relaxed(&value)
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) \
@ -71,6 +73,8 @@ extern "C" {
_Py_atomic_store_uint16_relaxed(&value, new_value)
#define FT_ATOMIC_STORE_UINT32_RELAXED(value, new_value) \
_Py_atomic_store_uint32_relaxed(&value, new_value)
#define FT_ATOMIC_STORE_UINT64_RELAXED(value, new_value) \
_Py_atomic_store_uint64_relaxed(&value, new_value)
#define FT_ATOMIC_STORE_CHAR_RELAXED(value, new_value) \
_Py_atomic_store_char_relaxed(&value, new_value)
#define FT_ATOMIC_LOAD_CHAR_RELAXED(value) \
@ -125,6 +129,8 @@ extern "C" {
_Py_atomic_load_ullong_relaxed(&value)
#define FT_ATOMIC_ADD_SSIZE(value, new_value) \
(void)_Py_atomic_add_ssize(&value, new_value)
#define FT_ATOMIC_ADD_UINT64(value, new_value) \
(void)_Py_atomic_add_uint64(&value, new_value)
#define FT_MUTEX_LOCK(lock) PyMutex_Lock(lock)
#define FT_MUTEX_UNLOCK(lock) PyMutex_Unlock(lock)
@ -144,6 +150,7 @@ extern "C" {
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT16_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT32_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT64_RELAXED(value) value
#define FT_ATOMIC_LOAD_ULONG_RELAXED(value) value
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
@ -155,6 +162,7 @@ extern "C" {
#define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_UINT16_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_UINT32_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_UINT64_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_LOAD_CHAR_RELAXED(value) value
#define FT_ATOMIC_STORE_CHAR_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_LOAD_UCHAR_RELAXED(value) value
@ -182,6 +190,7 @@ extern "C" {
#define FT_ATOMIC_LOAD_ULLONG_RELAXED(value) value
#define FT_ATOMIC_STORE_ULLONG_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_ADD_SSIZE(value, new_value) (void)(value += new_value)
#define FT_ATOMIC_ADD_UINT64(value, new_value) (void)(value += new_value)
#define FT_MUTEX_LOCK(lock) do {} while (0)
#define FT_MUTEX_UNLOCK(lock) do {} while (0)

View file

@ -49,6 +49,14 @@ class ABCMeta(type):
cls._abc_cache = WeakSet()
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
# Performance optimization for common case
cls._abc_should_check_subclasses = False
if "__subclasses__" in namespace:
cls._abc_should_check_subclasses = True
for base in bases:
if hasattr(base, "_abc_should_check_subclasses"):
base._abc_should_check_subclasses = True
return cls
def register(cls, subclass):
@ -65,8 +73,20 @@ class ABCMeta(type):
if issubclass(cls, subclass):
# This would create a cycle, which is bad for the algorithm below
raise RuntimeError("Refusing to create an inheritance cycle")
# Add registry entry
cls._abc_registry.add(subclass)
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
# Recursively register the subclass in all ABC bases,
# to avoid recursive lookups down the class tree.
# >>> class Ancestor1(ABC): pass
# >>> class Ancestor2(Ancestor1): pass
# >>> class Other: pass
# >>> Ancestor2.register(Other) # calls Ancestor1.register(Other)
# >>> issubclass(Other, Ancestor2) is True
# >>> issubclass(Other, Ancestor1) is True # already in registry
for base in cls.__bases__:
if hasattr(base, "_abc_registry"):
base.register(subclass)
return subclass
def _dump_registry(cls, file=None):
@ -132,16 +152,25 @@ class ABCMeta(type):
if cls in getattr(subclass, '__mro__', ()):
cls._abc_cache.add(subclass)
return True
# Fast path: check subclass is in weakset directly.
if subclass in cls._abc_registry:
cls._abc_cache.add(subclass)
return True
# Check if it's a subclass of a registered class (recursive)
for rcls in cls._abc_registry:
if issubclass(subclass, rcls):
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):
cls._abc_cache.add(subclass)
return True
# Check if it's a subclass of a subclass (recursive).
# If __subclasses__ contain only ABCs,
# calling issubclass(...) will trigger the same __subclasscheck__
# on *every* element of class inheritance tree.
# Performing that only in resence of `def __subclasses__()` classmethod
if cls._abc_should_check_subclasses:
for scls in cls.__subclasses__():
if issubclass(subclass, scls):
cls._abc_cache.add(subclass)
return True
# No dice; update negative cache
cls._abc_negative_cache.add(subclass)
return False

View file

@ -104,7 +104,7 @@ else:
"""
def __new__(mcls, name, bases, namespace, /, **kwargs):
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
_abc_init(cls)
_abc_init(cls, bases, namespace)
return cls
def register(cls, subclass):

View file

@ -70,6 +70,25 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
class TestABC(unittest.TestCase):
def check_isinstance(self, obj, target_class):
self.assertIsInstance(obj, target_class)
self.assertIsInstance(obj, (target_class,))
self.assertIsInstance(obj, target_class | int)
def check_not_isinstance(self, obj, target_class):
self.assertNotIsInstance(obj, target_class)
self.assertNotIsInstance(obj, (target_class,))
self.assertNotIsInstance(obj, target_class | int)
def check_issubclass(self, klass, target_class):
self.assertIsSubclass(klass, target_class)
self.assertIsSubclass(klass, (target_class,))
self.assertIsSubclass(klass, target_class | int)
def check_not_issubclass(self, klass, target_class):
self.assertNotIsSubclass(klass, target_class)
self.assertNotIsSubclass(klass, (target_class,))
self.assertNotIsSubclass(klass, target_class | int)
def test_ABC_helper(self):
# create an ABC using the helper class and perform basic checks
@ -270,29 +289,75 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
class C(metaclass=meta):
pass
def test_isinstance_direct_inheritance(self):
class A(metaclass=abc_ABCMeta):
pass
class B(A):
pass
class C(A):
pass
a = A()
b = B()
c = C()
# trigger caching
for _ in range(2):
self.check_isinstance(a, A)
self.check_not_isinstance(a, B)
self.check_not_isinstance(a, C)
self.check_isinstance(b, B)
self.check_isinstance(b, A)
self.check_not_isinstance(b, C)
self.check_isinstance(c, C)
self.check_isinstance(c, A)
self.check_not_isinstance(c, B)
self.check_issubclass(B, A)
self.check_issubclass(C, A)
self.check_not_issubclass(B, C)
self.check_not_issubclass(C, B)
self.check_not_issubclass(A, B)
self.check_not_issubclass(A, C)
def test_registration_basics(self):
class A(metaclass=abc_ABCMeta):
pass
class B(object):
pass
a = A()
b = B()
self.assertNotIsSubclass(B, A)
self.assertNotIsSubclass(B, (A,))
self.assertNotIsInstance(b, A)
self.assertNotIsInstance(b, (A,))
# trigger caching
for _ in range(2):
self.check_not_issubclass(B, A)
self.check_not_isinstance(b, A)
self.check_not_issubclass(A, B)
self.check_not_isinstance(a, B)
B1 = A.register(B)
self.assertIsSubclass(B, A)
self.assertIsSubclass(B, (A,))
self.assertIsInstance(b, A)
self.assertIsInstance(b, (A,))
self.assertIs(B1, B)
# trigger caching
for _ in range(2):
self.check_issubclass(B, A)
self.check_isinstance(b, A)
self.assertIs(B1, B)
self.check_not_issubclass(A, B)
self.check_not_isinstance(a, B)
class C(B):
pass
c = C()
self.assertIsSubclass(C, A)
self.assertIsSubclass(C, (A,))
self.assertIsInstance(c, A)
self.assertIsInstance(c, (A,))
# trigger caching
for _ in range(2):
self.check_issubclass(C, A)
self.check_isinstance(c, A)
self.check_not_issubclass(A, C)
self.check_not_isinstance(a, C)
def test_register_as_class_deco(self):
class A(metaclass=abc_ABCMeta):
@ -377,39 +442,95 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
pass
self.assertIsSubclass(A, A)
self.assertIsSubclass(A, (A,))
class B(metaclass=abc_ABCMeta):
pass
self.assertNotIsSubclass(A, B)
self.assertNotIsSubclass(A, (B,))
self.assertNotIsSubclass(B, A)
self.assertNotIsSubclass(B, (A,))
class C(metaclass=abc_ABCMeta):
pass
A.register(B)
class B1(B):
pass
self.assertIsSubclass(B1, A)
self.assertIsSubclass(B1, (A,))
# trigger caching
for _ in range(2):
self.assertIsSubclass(B1, A)
self.assertIsSubclass(B1, (A,))
class C1(C):
pass
B1.register(C1)
self.assertNotIsSubclass(C, B)
self.assertNotIsSubclass(C, (B,))
self.assertNotIsSubclass(C, B1)
self.assertNotIsSubclass(C, (B1,))
self.assertIsSubclass(C1, A)
self.assertIsSubclass(C1, (A,))
self.assertIsSubclass(C1, B)
self.assertIsSubclass(C1, (B,))
self.assertIsSubclass(C1, B1)
self.assertIsSubclass(C1, (B1,))
# trigger caching
for _ in range(2):
self.assertNotIsSubclass(C, B)
self.assertNotIsSubclass(C, (B,))
self.assertNotIsSubclass(C, B1)
self.assertNotIsSubclass(C, (B1,))
self.assertIsSubclass(C1, A)
self.assertIsSubclass(C1, (A,))
self.assertIsSubclass(C1, B)
self.assertIsSubclass(C1, (B,))
self.assertIsSubclass(C1, B1)
self.assertIsSubclass(C1, (B1,))
C1.register(int)
class MyInt(int):
pass
self.assertIsSubclass(MyInt, A)
self.assertIsSubclass(MyInt, (A,))
self.assertIsInstance(42, A)
self.assertIsInstance(42, (A,))
# trigger caching
for _ in range(2):
self.assertIsSubclass(MyInt, A)
self.assertIsSubclass(MyInt, (A,))
self.assertIsInstance(42, A)
self.assertIsInstance(42, (A,))
def test_custom_subclasses(self):
class A: pass
class B(A): pass
class C: pass
class D(C): pass
class Root(metaclass=abc_ABCMeta): pass
class Parent1(Root):
@classmethod
def __subclasses__(cls):
return [A]
class Parent2(Root):
__subclasses__ = lambda: [A]
# trigger caching
for _ in range(2):
self.check_isinstance(A(), Parent1)
self.check_isinstance(B(), Parent1)
self.check_issubclass(A, Parent1)
self.check_issubclass(B, Parent1)
self.check_not_isinstance(C(), Parent1)
self.check_not_isinstance(D(), Parent1)
self.check_not_issubclass(C, Parent1)
self.check_not_issubclass(D, Parent1)
self.check_isinstance(A(), Parent2)
self.check_isinstance(B(), Parent2)
self.check_issubclass(A, Parent2)
self.check_issubclass(B, Parent2)
self.check_not_isinstance(C(), Parent2)
self.check_not_isinstance(D(), Parent2)
self.check_not_issubclass(C, Parent2)
self.check_not_issubclass(D, Parent2)
self.check_isinstance(A(), Root)
self.check_isinstance(B(), Root)
self.check_issubclass(A, Root)
self.check_issubclass(B, Root)
self.check_not_isinstance(C(), Root)
self.check_not_isinstance(D(), Root)
self.check_not_issubclass(C, Root)
self.check_not_issubclass(D, Root)
def test_issubclass_bad_arguments(self):
class A(metaclass=abc_ABCMeta):
@ -460,8 +581,32 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
with self.assertRaisesRegex(CustomError, exc_msg):
issubclass(int, S)
def test_subclasshook(self):
def test_issubclass_bad_class(self):
class A(metaclass=abc.ABCMeta):
pass
A._abc_impl = 1
error_msg = "_abc_impl is set to a wrong type"
with self.assertRaisesRegex(TypeError, error_msg):
issubclass(A, A)
class B(metaclass=_py_abc.ABCMeta):
pass
B._abc_cache = 1
error_msg = "argument of type 'int' is not a container or iterable"
with self.assertRaisesRegex(TypeError, error_msg):
issubclass(B, B)
class C(metaclass=_py_abc.ABCMeta):
pass
C._abc_negative_cache = 1
with self.assertRaisesRegex(TypeError, error_msg):
issubclass(C, C)
def test_subclasshook(self):
class A(metaclass=abc_ABCMeta):
@classmethod
def __subclasshook__(cls, C):
if cls is A:
@ -478,6 +623,26 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
self.assertNotIsSubclass(C, A)
self.assertNotIsSubclass(C, (A,))
def test_subclasshook_exception(self):
# Check that issubclass() propagates exceptions raised by
# __subclasshook__.
class CustomError(Exception): ...
exc_msg = "exception from __subclasshook__"
class A(metaclass=abc_ABCMeta):
@classmethod
def __subclasshook__(cls, C):
raise CustomError(exc_msg)
with self.assertRaisesRegex(CustomError, exc_msg):
issubclass(A, A)
class B(A):
pass
with self.assertRaisesRegex(CustomError, exc_msg):
issubclass(B, A)
class C:
pass
with self.assertRaisesRegex(CustomError, exc_msg):
issubclass(C, A)
def test_all_new_methods_are_called(self):
class A(metaclass=abc_ABCMeta):
pass
@ -522,7 +687,6 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
self.assertEqual(A.__abstractmethods__, set())
A()
def test_update_new_abstractmethods(self):
class A(metaclass=abc_ABCMeta):
@abc.abstractmethod

View file

@ -353,6 +353,28 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
with support.infinite_recursion(25):
self.assertRaises(RecursionError, issubclass, X(), int)
def test_custom_subclasses_are_ignored(self):
class A: pass
class B: pass
class Parent1:
@classmethod
def __subclasses__(cls):
return [A, B]
class Parent2:
__subclasses__ = lambda: [A, B]
self.assertNotIsInstance(A(), Parent1)
self.assertNotIsInstance(B(), Parent1)
self.assertNotIsSubclass(A, Parent1)
self.assertNotIsSubclass(B, Parent1)
self.assertNotIsInstance(A(), Parent2)
self.assertNotIsInstance(B(), Parent2)
self.assertNotIsSubclass(A, Parent2)
self.assertNotIsSubclass(B, Parent2)
def blowstack(fxn, arg, compare_to):
# Make sure that calling isinstance with a deeply nested tuple for its

View file

@ -0,0 +1,2 @@
Reduce memory usage by :meth:`~type.__subclasscheck__` for
:class:`abc.ABCMeta` with large class trees.

View file

@ -35,21 +35,13 @@ get_abc_state(PyObject *module)
static inline uint64_t
get_invalidation_counter(_abcmodule_state *state)
{
#ifdef Py_GIL_DISABLED
return _Py_atomic_load_uint64(&state->abc_invalidation_counter);
#else
return state->abc_invalidation_counter;
#endif
return FT_ATOMIC_LOAD_UINT64_RELAXED(state->abc_invalidation_counter);
}
static inline void
increment_invalidation_counter(_abcmodule_state *state)
{
#ifdef Py_GIL_DISABLED
_Py_atomic_add_uint64(&state->abc_invalidation_counter, 1);
#else
state->abc_invalidation_counter++;
#endif
FT_ATOMIC_ADD_UINT64(state->abc_invalidation_counter, 1);
}
/* This object stores internal state for ABCs.
@ -65,6 +57,7 @@ typedef struct {
PyObject *_abc_cache;
PyObject *_abc_negative_cache;
uint64_t _abc_negative_cache_version;
uint8_t _abc_should_check_subclasses;
} _abc_data;
#define _abc_data_CAST(op) ((_abc_data *)(op))
@ -72,21 +65,25 @@ typedef struct {
static inline uint64_t
get_cache_version(_abc_data *impl)
{
#ifdef Py_GIL_DISABLED
return _Py_atomic_load_uint64(&impl->_abc_negative_cache_version);
#else
return impl->_abc_negative_cache_version;
#endif
return FT_ATOMIC_LOAD_UINT64_RELAXED(impl->_abc_negative_cache_version);
}
static inline void
set_cache_version(_abc_data *impl, uint64_t version)
{
#ifdef Py_GIL_DISABLED
_Py_atomic_store_uint64(&impl->_abc_negative_cache_version, version);
#else
impl->_abc_negative_cache_version = version;
#endif
FT_ATOMIC_STORE_UINT64_RELAXED(impl->_abc_negative_cache_version, version);
}
static inline uint8_t
get_should_check_subclasses(_abc_data *impl)
{
return FT_ATOMIC_LOAD_UINT8_RELAXED(impl->_abc_should_check_subclasses);
}
static inline void
set_should_check_subclasses(_abc_data *impl)
{
FT_ATOMIC_STORE_UINT8_RELAXED(impl->_abc_should_check_subclasses, 1);
}
static int
@ -139,6 +136,7 @@ abc_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->_abc_cache = NULL;
self->_abc_negative_cache = NULL;
self->_abc_negative_cache_version = get_invalidation_counter(state);
self->_abc_should_check_subclasses = 0;
return (PyObject *) self;
}
@ -177,6 +175,30 @@ _get_impl(PyObject *module, PyObject *self)
return (_abc_data *)impl;
}
/* If class is inherited from ABC, set data to point to internal ABC state of class, and return 1.
If object is not inherited from ABC, return 0.
If error is encountered, return -1.
*/
static int
_get_optional_impl(_abcmodule_state *state, PyObject *self, _abc_data **data)
{
assert(data != NULL);
PyObject *impl = NULL;
int res = PyObject_GetOptionalAttr(self, &_Py_ID(_abc_impl), &impl);
if (res <= 0) {
*data = NULL;
return res;
}
if (!Py_IS_TYPE(impl, state->_abc_data_type)) {
PyErr_SetString(PyExc_TypeError, "_abc_impl is set to a wrong type");
Py_DECREF(impl);
*data = NULL;
return -1;
}
*data = (_abc_data *)impl;
return 1;
}
static int
_in_weak_set(_abc_data *impl, PyObject **pset, PyObject *obj)
{
@ -347,11 +369,12 @@ _abc__get_dump(PyObject *module, PyObject *self)
}
PyObject *res;
Py_BEGIN_CRITICAL_SECTION(impl);
res = Py_BuildValue("NNNK",
res = Py_BuildValue("NNNKK",
PySet_New(impl->_abc_registry),
PySet_New(impl->_abc_cache),
PySet_New(impl->_abc_negative_cache),
get_cache_version(impl));
get_cache_version(impl),
get_should_check_subclasses(impl));
Py_END_CRITICAL_SECTION();
Py_DECREF(impl);
return res;
@ -359,7 +382,7 @@ _abc__get_dump(PyObject *module, PyObject *self)
// Compute set of abstract method names.
static int
compute_abstract_methods(PyObject *self)
compute_abstract_methods(PyObject *self, PyObject *bases, PyObject *ns)
{
int ret = -1;
PyObject *abstracts = PyFrozenSet_New(NULL);
@ -367,19 +390,13 @@ compute_abstract_methods(PyObject *self)
return -1;
}
PyObject *ns = NULL, *items = NULL, *bases = NULL; // Py_XDECREF()ed on error.
/* Stage 1: direct abstract methods. */
ns = PyObject_GetAttr(self, &_Py_ID(__dict__));
if (!ns) {
goto error;
}
// We can't use PyDict_Next(ns) even when ns is dict because
// _PyObject_IsAbstract() can mutate ns.
items = PyMapping_Items(ns);
PyObject *items = PyMapping_Items(ns);
if (!items) {
goto error;
Py_DECREF(abstracts);
return -1;
}
assert(PyList_Check(items));
for (Py_ssize_t pos = 0; pos < PyList_GET_SIZE(items); pos++) {
@ -414,15 +431,6 @@ compute_abstract_methods(PyObject *self)
}
/* Stage 2: inherited abstract methods. */
bases = PyObject_GetAttr(self, &_Py_ID(__bases__));
if (!bases) {
goto error;
}
if (!PyTuple_Check(bases)) {
PyErr_SetString(PyExc_TypeError, "__bases__ is not tuple");
goto error;
}
for (Py_ssize_t pos = 0; pos < PyTuple_GET_SIZE(bases); pos++) {
PyObject *item = PyTuple_GET_ITEM(bases, pos); // borrowed
PyObject *base_abstracts, *iter;
@ -475,12 +483,36 @@ compute_abstract_methods(PyObject *self)
ret = 0;
error:
Py_DECREF(abstracts);
Py_XDECREF(ns);
Py_XDECREF(items);
Py_XDECREF(bases);
Py_DECREF(items);
return ret;
}
/*
* Notify base classes that child one has __subclasses__ overriden.
* Used as performance optimization in __subclasscheck__
*/
static int
_abc_notify_subclasses_override(_abcmodule_state *state, PyObject *data, PyObject *bases)
{
set_should_check_subclasses((_abc_data*) data);
for (Py_ssize_t pos = 0; pos < PyTuple_GET_SIZE(bases); pos++) {
PyObject *base_class = PyTuple_GET_ITEM(bases, pos); // borrowed
_abc_data *base_impl = NULL;
int base_is_abc = _get_optional_impl(state, base_class, &base_impl);
if (base_is_abc < 0) {
return -1;
}
if (base_is_abc == 0) {
continue;
}
set_should_check_subclasses(base_impl);
Py_DECREF(base_impl);
}
return 0;
}
#define COLLECTION_FLAGS (Py_TPFLAGS_SEQUENCE | Py_TPFLAGS_MAPPING)
/*[clinic input]
@ -488,18 +520,21 @@ error:
_abc._abc_init
self: object
bases: object(subclass_of="&PyTuple_Type")
namespace: object(subclass_of="&PyDict_Type")
/
Internal ABC helper for class set-up. Should be never used outside abc module.
[clinic start generated code]*/
static PyObject *
_abc__abc_init(PyObject *module, PyObject *self)
/*[clinic end generated code: output=594757375714cda1 input=0b3513f947736d39]*/
_abc__abc_init_impl(PyObject *module, PyObject *self, PyObject *bases,
PyObject *namespace)
/*[clinic end generated code: output=a410180fefc86056 input=a984e4f7d36d6298]*/
{
_abcmodule_state *state = get_abc_state(module);
PyObject *data;
if (compute_abstract_methods(self) < 0) {
if (compute_abstract_methods(self, bases, namespace) < 0) {
return NULL;
}
@ -508,6 +543,12 @@ _abc__abc_init(PyObject *module, PyObject *self)
if (data == NULL) {
return NULL;
}
if (PyDict_ContainsString(namespace, "__subclasses__")) {
if (_abc_notify_subclasses_override(state, data, bases) < 0) {
Py_DECREF(data);
return NULL;
}
}
if (PyObject_SetAttr(self, &_Py_ID(_abc_impl), data) < 0) {
Py_DECREF(data);
return NULL;
@ -580,6 +621,7 @@ _abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass)
if (result < 0) {
return NULL;
}
/* Add registry entry */
_abc_data *impl = _get_impl(module, self);
if (impl == NULL) {
return NULL;
@ -591,7 +633,43 @@ _abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass)
Py_DECREF(impl);
/* Invalidate negative cache */
increment_invalidation_counter(get_abc_state(module));
_abcmodule_state *state = get_abc_state(module);
increment_invalidation_counter(state);
/*
* Recursively register the subclass in all ABC bases,
* to avoid recursive lookups down the class tree.
* >>> class Ancestor1(ABC): pass
* >>> class Ancestor2(Ancestor1): pass
* >>> class Other: pass
* >>> Ancestor2.register(Other) # calls Ancestor1.register(Other)
* >>> issubclass(Other, Ancestor2) is True
* >>> issubclass(Other, Ancestor1) is True # already in registry
*/
PyObject *bases = PyObject_GetAttr(self, &_Py_ID(__bases__));
if (!bases) {
return NULL;
}
if (!PyTuple_Check(bases)) {
PyErr_SetString(PyExc_TypeError, "__bases__ is not tuple");
goto error;
}
for (Py_ssize_t pos = 0; pos < PyTuple_GET_SIZE(bases); pos++) {
PyObject *base = PyTuple_GET_ITEM(bases, pos); // borrowed
int base_is_abc = PyObject_HasAttrWithError(base, &_Py_ID(_abc_impl));
if (base_is_abc < 0) {
goto error;
}
if (base_is_abc == 0) {
continue;
}
PyObject *res = PyObject_CallMethod(base, "register", "O", subclass);
Py_XDECREF(res);
if (!res) {
goto error;
}
}
Py_DECREF(bases);
/* Set Py_TPFLAGS_SEQUENCE or Py_TPFLAGS_MAPPING flag */
if (PyType_Check(self)) {
@ -604,6 +682,10 @@ _abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass)
}
}
return Py_NewRef(subclass);
error:
Py_DECREF(bases);
return NULL;
}
@ -804,31 +886,38 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
goto end;
}
/* 6. Check if it's a subclass of a subclass (recursive). */
subclasses = PyObject_CallMethod(self, "__subclasses__", NULL);
if (subclasses == NULL) {
goto end;
}
if (!PyList_Check(subclasses)) {
PyErr_SetString(PyExc_TypeError, "__subclasses__() must return a list");
goto end;
}
for (pos = 0; pos < PyList_GET_SIZE(subclasses); pos++) {
PyObject *scls = PyList_GetItemRef(subclasses, pos);
if (scls == NULL) {
/* 6. Check if it's a subclass of a subclass (recursive).
* If __subclasses__ contain only ABCs,
* calling issubclass(...) will trigger the same __subclasscheck__
* on *every* element of class inheritance tree.
* Performing that only in resence of `def __subclasses__()` classmethod
*/
if (get_should_check_subclasses(impl)) {
subclasses = PyObject_CallMethod(self, "__subclasses__", NULL);
if (subclasses == NULL) {
goto end;
}
int r = PyObject_IsSubclass(subclass, scls);
Py_DECREF(scls);
if (r > 0) {
if (_add_to_weak_set(impl, &impl->_abc_cache, subclass) < 0) {
if (!PyList_Check(subclasses)) {
PyErr_SetString(PyExc_TypeError, "__subclasses__() must return a list");
goto end;
}
for (pos = 0; pos < PyList_GET_SIZE(subclasses); pos++) {
PyObject *scls = PyList_GetItemRef(subclasses, pos);
if (scls == NULL) {
goto end;
}
int r = PyObject_IsSubclass(subclass, scls);
Py_DECREF(scls);
if (r > 0) {
if (_add_to_weak_set(impl, &impl->_abc_cache, subclass) < 0) {
goto end;
}
result = Py_True;
goto end;
}
if (r < 0) {
goto end;
}
result = Py_True;
goto end;
}
if (r < 0) {
goto end;
}
}
@ -849,7 +938,7 @@ static int
subclasscheck_check_registry(_abc_data *impl, PyObject *subclass,
PyObject **result)
{
// Fast path: check subclass is in weakref directly.
// Fast path: check subclass is in weakset directly.
int ret = _in_weak_set(impl, &impl->_abc_registry, subclass);
if (ret < 0) {
*result = NULL;

View file

@ -40,13 +40,45 @@ PyDoc_STRVAR(_abc__get_dump__doc__,
{"_get_dump", (PyCFunction)_abc__get_dump, METH_O, _abc__get_dump__doc__},
PyDoc_STRVAR(_abc__abc_init__doc__,
"_abc_init($module, self, /)\n"
"_abc_init($module, self, bases, namespace, /)\n"
"--\n"
"\n"
"Internal ABC helper for class set-up. Should be never used outside abc module.");
#define _ABC__ABC_INIT_METHODDEF \
{"_abc_init", (PyCFunction)_abc__abc_init, METH_O, _abc__abc_init__doc__},
{"_abc_init", _PyCFunction_CAST(_abc__abc_init), METH_FASTCALL, _abc__abc_init__doc__},
static PyObject *
_abc__abc_init_impl(PyObject *module, PyObject *self, PyObject *bases,
PyObject *namespace);
static PyObject *
_abc__abc_init(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
PyObject *self;
PyObject *bases;
PyObject *namespace;
if (!_PyArg_CheckPositional("_abc_init", nargs, 3, 3)) {
goto exit;
}
self = args[0];
if (!PyTuple_Check(args[1])) {
_PyArg_BadArgument("_abc_init", "argument 2", "tuple", args[1]);
goto exit;
}
bases = args[1];
if (!PyDict_Check(args[2])) {
_PyArg_BadArgument("_abc_init", "argument 3", "dict", args[2]);
goto exit;
}
namespace = args[2];
return_value = _abc__abc_init_impl(module, self, bases, namespace);
exit:
return return_value;
}
PyDoc_STRVAR(_abc__abc_register__doc__,
"_abc_register($module, self, subclass, /)\n"
@ -161,4 +193,4 @@ _abc_get_cache_token(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return _abc_get_cache_token_impl(module);
}
/*[clinic end generated code: output=1989b6716c950e17 input=a9049054013a1b77]*/
/*[clinic end generated code: output=9fa68621578b46d0 input=a9049054013a1b77]*/