mirror of
https://github.com/python/cpython.git
synced 2025-12-23 09:19:18 +00:00
Merge a2fc5622dd into f9704f1d84
This commit is contained in:
commit
03bb6a0d20
9 changed files with 484 additions and 110 deletions
|
|
@ -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
|
||||
--------
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Reduce memory usage by :meth:`~type.__subclasscheck__` for
|
||||
:class:`abc.ABCMeta` with large class trees.
|
||||
229
Modules/_abc.c
229
Modules/_abc.c
|
|
@ -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;
|
||||
|
|
|
|||
38
Modules/clinic/_abc.c.h
generated
38
Modules/clinic/_abc.c.h
generated
|
|
@ -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]*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue