bpo-31333: Re-implement ABCMeta in C (#5273)

This adds C versions of methods used by ABCMeta that
improve performance of various ABC operations.
This commit is contained in:
Ivan Levkivskyi 2018-02-18 12:41:58 +00:00 committed by GitHub
parent 667b91a5e2
commit 03e3c340a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1638 additions and 534 deletions

View File

@ -853,6 +853,12 @@ Optimizations
* Constant folding is moved from peephole optimizer to new AST optimizer.
(Contributed by Eugene Toder and INADA Naoki in :issue:`29469`)
* Most functions and methods in :mod:`abc` have been rewrittent in C.
This makes creation of abstract base classes, and calling :func:`isinstance`
and :func:`issubclass` on them 1.5x faster. This also reduces Python
start-up time by up to 10%. (Contributed by Ivan Levkivskyi and INADA Naoki
in :issue:`31333`)
Build and C API Changes
=======================

145
Lib/_py_abc.py Normal file
View File

@ -0,0 +1,145 @@
from _weakrefset import WeakSet
def get_cache_token():
"""Returns the current ABC cache token.
The token is an opaque object (supporting equality testing) identifying the
current version of the ABC cache for virtual subclasses. The token changes
with every call to ``register()`` on any ABC.
"""
return ABCMeta._abc_invalidation_counter
class ABCMeta(type):
"""Metaclass for defining Abstract Base Classes (ABCs).
Use this metaclass to create an ABC. An ABC can be subclassed
directly, and then acts as a mix-in class. You can also register
unrelated concrete classes (even built-in classes) and unrelated
ABCs as 'virtual subclasses' -- these and their descendants will
be considered subclasses of the registering ABC by the built-in
issubclass() function, but the registering ABC won't show up in
their MRO (Method Resolution Order) nor will method
implementations defined by the registering ABC be callable (not
even via super()).
"""
# A global counter that is incremented each time a class is
# registered as a virtual subclass of anything. It forces the
# negative cache to be cleared before its next use.
# Note: this counter is private. Use `abc.get_cache_token()` for
# external code.
_abc_invalidation_counter = 0
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
return cls
def register(cls, subclass):
"""Register a virtual subclass of an ABC.
Returns the subclass, to allow usage as a class decorator.
"""
if not isinstance(subclass, type):
raise TypeError("Can only register classes")
if issubclass(subclass, cls):
return subclass # Already a subclass
# Subtle: test for cycles *after* testing for "already a subclass";
# this means we allow X.register(X) and interpret it as a no-op.
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)
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
return subclass
def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
print(f"Inv. counter: {get_cache_token()}", file=file)
for name in cls.__dict__:
if name.startswith("_abc_"):
value = getattr(cls, name)
if isinstance(value, WeakSet):
value = set(value)
print(f"{name}: {value!r}", file=file)
def _abc_registry_clear(cls):
"""Clear the registry (for debugging or testing)."""
cls._abc_registry.clear()
def _abc_caches_clear(cls):
"""Clear the caches (for debugging or testing)."""
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__
if subclass in cls._abc_cache:
return True
subtype = type(instance)
if subtype is subclass:
if (cls._abc_negative_cache_version ==
ABCMeta._abc_invalidation_counter and
subclass in cls._abc_negative_cache):
return False
# Fall back to the subclass check.
return cls.__subclasscheck__(subclass)
return any(cls.__subclasscheck__(c) for c in (subclass, subtype))
def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
# Check cache
if subclass in cls._abc_cache:
return True
# Check negative cache; may have to invalidate
if cls._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:
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)
else:
cls._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)
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
# No dice; update negative cache
cls._abc_negative_cache.add(subclass)
return False

View File

@ -3,8 +3,6 @@
"""Abstract Base Classes (ABCs) according to PEP 3119."""
from _weakrefset import WeakSet
def abstractmethod(funcobj):
"""A decorator indicating abstract methods.
@ -27,8 +25,7 @@ def abstractmethod(funcobj):
class abstractclassmethod(classmethod):
"""
A decorator indicating abstract classmethods.
"""A decorator indicating abstract classmethods.
Similar to abstractmethod.
@ -51,8 +48,7 @@ class abstractclassmethod(classmethod):
class abstractstaticmethod(staticmethod):
"""
A decorator indicating abstract staticmethods.
"""A decorator indicating abstract staticmethods.
Similar to abstractmethod.
@ -75,8 +71,7 @@ class abstractstaticmethod(staticmethod):
class abstractproperty(property):
"""
A decorator indicating abstract properties.
"""A decorator indicating abstract properties.
Requires that the metaclass is ABCMeta or derived from it. A
class that has a metaclass derived from ABCMeta cannot be
@ -106,131 +101,66 @@ class abstractproperty(property):
__isabstractmethod__ = True
class ABCMeta(type):
try:
from _abc import (get_cache_token, _abc_init, _abc_register,
_abc_instancecheck, _abc_subclasscheck, _get_dump,
_reset_registry, _reset_caches)
except ImportError:
from _py_abc import ABCMeta, get_cache_token
ABCMeta.__module__ = 'abc'
else:
class ABCMeta(type):
"""Metaclass for defining Abstract Base Classes (ABCs).
"""Metaclass for defining Abstract Base Classes (ABCs).
Use this metaclass to create an ABC. An ABC can be subclassed
directly, and then acts as a mix-in class. You can also register
unrelated concrete classes (even built-in classes) and unrelated
ABCs as 'virtual subclasses' -- these and their descendants will
be considered subclasses of the registering ABC by the built-in
issubclass() function, but the registering ABC won't show up in
their MRO (Method Resolution Order) nor will method
implementations defined by the registering ABC be callable (not
even via super()).
"""
# A global counter that is incremented each time a class is
# registered as a virtual subclass of anything. It forces the
# negative cache to be cleared before its next use.
# Note: this counter is private. Use `abc.get_cache_token()` for
# external code.
_abc_invalidation_counter = 0
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
return cls
def register(cls, subclass):
"""Register a virtual subclass of an ABC.
Returns the subclass, to allow usage as a class decorator.
Use this metaclass to create an ABC. An ABC can be subclassed
directly, and then acts as a mix-in class. You can also register
unrelated concrete classes (even built-in classes) and unrelated
ABCs as 'virtual subclasses' -- these and their descendants will
be considered subclasses of the registering ABC by the built-in
issubclass() function, but the registering ABC won't show up in
their MRO (Method Resolution Order) nor will method
implementations defined by the registering ABC be callable (not
even via super()).
"""
if not isinstance(subclass, type):
raise TypeError("Can only register classes")
if issubclass(subclass, cls):
return subclass # Already a subclass
# Subtle: test for cycles *after* testing for "already a subclass";
# this means we allow X.register(X) and interpret it as a no-op.
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)
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
return subclass
def __new__(mcls, name, bases, namespace, **kwargs):
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
_abc_init(cls)
return cls
def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print("Class: %s.%s" % (cls.__module__, cls.__qualname__), file=file)
print("Inv.counter: %s" % ABCMeta._abc_invalidation_counter, file=file)
for name in cls.__dict__:
if name.startswith("_abc_"):
value = getattr(cls, name)
if isinstance(value, WeakSet):
value = set(value)
print("%s: %r" % (name, value), file=file)
def register(cls, subclass):
"""Register a virtual subclass of an ABC.
def __instancecheck__(cls, instance):
"""Override for isinstance(instance, cls)."""
# Inline the cache checking
subclass = instance.__class__
if subclass in cls._abc_cache:
return True
subtype = type(instance)
if subtype is subclass:
if (cls._abc_negative_cache_version ==
ABCMeta._abc_invalidation_counter and
subclass in cls._abc_negative_cache):
return False
# Fall back to the subclass check.
return cls.__subclasscheck__(subclass)
return any(cls.__subclasscheck__(c) for c in {subclass, subtype})
Returns the subclass, to allow usage as a class decorator.
"""
return _abc_register(cls, subclass)
def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
# Check cache
if subclass in cls._abc_cache:
return True
# Check negative cache; may have to invalidate
if cls._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:
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)
else:
cls._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)
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
# No dice; update negative cache
cls._abc_negative_cache.add(subclass)
return False
def __instancecheck__(cls, instance):
"""Override for isinstance(instance, cls)."""
return _abc_instancecheck(cls, instance)
def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
return _abc_subclasscheck(cls, subclass)
def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
print(f"Inv. counter: {get_cache_token()}", file=file)
(_abc_registry, _abc_cache, _abc_negative_cache,
_abc_negative_cache_version) = _get_dump(cls)
print(f"_abc_registry: {_abc_registry!r}", file=file)
print(f"_abc_cache: {_abc_cache!r}", file=file)
print(f"_abc_negative_cache: {_abc_negative_cache!r}", file=file)
print(f"_abc_negative_cache_version: {_abc_negative_cache_version!r}",
file=file)
def _abc_registry_clear(cls):
"""Clear the registry (for debugging or testing)."""
_reset_registry(cls)
def _abc_caches_clear(cls):
"""Clear the caches (for debugging or testing)."""
_reset_caches(cls)
class ABC(metaclass=ABCMeta):
@ -238,13 +168,3 @@ class ABC(metaclass=ABCMeta):
inheritance.
"""
__slots__ = ()
def get_cache_token():
"""Returns the current ABC cache token.
The token is an opaque object (supporting equality testing) identifying the
current version of the ABC cache for virtual subclasses. The token changes
with every call to ``register()`` on any ABC.
"""
return ABCMeta._abc_invalidation_counter

View File

@ -5,6 +5,13 @@ import sys
import warnings
from inspect import isabstract
from test import support
try:
from _abc import _get_dump
except ImportError:
def _get_dump(cls):
# For legacy Python version
return (cls._abc_registry, cls._abc_cache,
cls._abc_negative_cache, cls._abc_negative_cache_version)
def dash_R(the_module, test, indirect_test, huntrleaks):
@ -36,7 +43,7 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
if not isabstract(abc):
continue
for obj in abc.__subclasses__() + [abc]:
abcs[obj] = obj._abc_registry.copy()
abcs[obj] = _get_dump(obj)[0]
# bpo-31217: Integer pool to get a single integer object for the same
# value. The pool is used to prevent false alarm when checking for memory
@ -113,7 +120,6 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
def dash_R_cleanup(fs, ps, pic, zdc, abcs):
import gc, copyreg
import collections.abc
from weakref import WeakSet
# Restore some original values.
warnings.filters[:] = fs
@ -137,9 +143,10 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
abs_classes = filter(isabstract, abs_classes)
for abc in abs_classes:
for obj in abc.__subclasses__() + [abc]:
obj._abc_registry = abcs.get(obj, WeakSet()).copy()
obj._abc_cache.clear()
obj._abc_negative_cache.clear()
for ref in abcs.get(obj, set()):
if ref() is not None:
obj.register(ref())
obj._abc_caches_clear()
clear_caches()

View File

@ -1,422 +1,445 @@
# Copyright 2007 Google, Inc. All Rights Reserved.
# Licensed to PSF under a Contributor Agreement.
# Note: each test is run with Python and C versions of ABCMeta. Except for
# test_ABC_helper(), which assures that abc.ABC is an instance of abc.ABCMeta.
"""Unit tests for abc.py."""
import unittest
import abc
import _py_abc
from inspect import isabstract
def test_factory(abc_ABCMeta, abc_get_cache_token):
class TestLegacyAPI(unittest.TestCase):
class TestLegacyAPI(unittest.TestCase):
def test_abstractproperty_basics(self):
@abc.abstractproperty
def foo(self): pass
self.assertTrue(foo.__isabstractmethod__)
def bar(self): pass
self.assertFalse(hasattr(bar, "__isabstractmethod__"))
class C(metaclass=abc.ABCMeta):
def test_abstractproperty_basics(self):
@abc.abstractproperty
def foo(self): return 3
self.assertRaises(TypeError, C)
class D(C):
@property
def foo(self): return super().foo
self.assertEqual(D().foo, 3)
self.assertFalse(getattr(D.foo, "__isabstractmethod__", False))
def foo(self): pass
self.assertTrue(foo.__isabstractmethod__)
def bar(self): pass
self.assertFalse(hasattr(bar, "__isabstractmethod__"))
def test_abstractclassmethod_basics(self):
@abc.abstractclassmethod
def foo(cls): pass
self.assertTrue(foo.__isabstractmethod__)
@classmethod
def bar(cls): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc.ABCMeta):
@abc.abstractclassmethod
def foo(cls): return cls.__name__
self.assertRaises(TypeError, C)
class D(C):
@classmethod
def foo(cls): return super().foo()
self.assertEqual(D.foo(), 'D')
self.assertEqual(D().foo(), 'D')
def test_abstractstaticmethod_basics(self):
@abc.abstractstaticmethod
def foo(): pass
self.assertTrue(foo.__isabstractmethod__)
@staticmethod
def bar(): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc.ABCMeta):
@abc.abstractstaticmethod
def foo(): return 3
self.assertRaises(TypeError, C)
class D(C):
@staticmethod
def foo(): return 4
self.assertEqual(D.foo(), 4)
self.assertEqual(D().foo(), 4)
class TestABC(unittest.TestCase):
def test_ABC_helper(self):
# create an ABC using the helper class and perform basic checks
class C(abc.ABC):
@classmethod
@abc.abstractmethod
def foo(cls): return cls.__name__
self.assertEqual(type(C), abc.ABCMeta)
self.assertRaises(TypeError, C)
class D(C):
@classmethod
def foo(cls): return super().foo()
self.assertEqual(D.foo(), 'D')
def test_abstractmethod_basics(self):
@abc.abstractmethod
def foo(self): pass
self.assertTrue(foo.__isabstractmethod__)
def bar(self): pass
self.assertFalse(hasattr(bar, "__isabstractmethod__"))
def test_abstractproperty_basics(self):
@property
@abc.abstractmethod
def foo(self): pass
self.assertTrue(foo.__isabstractmethod__)
def bar(self): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def foo(self): return 3
self.assertRaises(TypeError, C)
class D(C):
@C.foo.getter
def foo(self): return super().foo
self.assertEqual(D().foo, 3)
def test_abstractclassmethod_basics(self):
@classmethod
@abc.abstractmethod
def foo(cls): pass
self.assertTrue(foo.__isabstractmethod__)
@classmethod
def bar(cls): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc.ABCMeta):
@classmethod
@abc.abstractmethod
def foo(cls): return cls.__name__
self.assertRaises(TypeError, C)
class D(C):
@classmethod
def foo(cls): return super().foo()
self.assertEqual(D.foo(), 'D')
self.assertEqual(D().foo(), 'D')
def test_abstractstaticmethod_basics(self):
@staticmethod
@abc.abstractmethod
def foo(): pass
self.assertTrue(foo.__isabstractmethod__)
@staticmethod
def bar(): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc.ABCMeta):
@staticmethod
@abc.abstractmethod
def foo(): return 3
self.assertRaises(TypeError, C)
class D(C):
@staticmethod
def foo(): return 4
self.assertEqual(D.foo(), 4)
self.assertEqual(D().foo(), 4)
def test_abstractmethod_integration(self):
for abstractthing in [abc.abstractmethod, abc.abstractproperty,
abc.abstractclassmethod,
abc.abstractstaticmethod]:
class C(metaclass=abc.ABCMeta):
@abstractthing
def foo(self): pass # abstract
def bar(self): pass # concrete
self.assertEqual(C.__abstractmethods__, {"foo"})
self.assertRaises(TypeError, C) # because foo is abstract
self.assertTrue(isabstract(C))
class C(metaclass=abc_ABCMeta):
@abc.abstractproperty
def foo(self): return 3
self.assertRaises(TypeError, C)
class D(C):
def bar(self): pass # concrete override of concrete
self.assertEqual(D.__abstractmethods__, {"foo"})
self.assertRaises(TypeError, D) # because foo is still abstract
self.assertTrue(isabstract(D))
@property
def foo(self): return super().foo
self.assertEqual(D().foo, 3)
self.assertFalse(getattr(D.foo, "__isabstractmethod__", False))
def test_abstractclassmethod_basics(self):
@abc.abstractclassmethod
def foo(cls): pass
self.assertTrue(foo.__isabstractmethod__)
@classmethod
def bar(cls): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc_ABCMeta):
@abc.abstractclassmethod
def foo(cls): return cls.__name__
self.assertRaises(TypeError, C)
class D(C):
@classmethod
def foo(cls): return super().foo()
self.assertEqual(D.foo(), 'D')
self.assertEqual(D().foo(), 'D')
def test_abstractstaticmethod_basics(self):
@abc.abstractstaticmethod
def foo(): pass
self.assertTrue(foo.__isabstractmethod__)
@staticmethod
def bar(): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc_ABCMeta):
@abc.abstractstaticmethod
def foo(): return 3
self.assertRaises(TypeError, C)
class D(C):
@staticmethod
def foo(): return 4
self.assertEqual(D.foo(), 4)
self.assertEqual(D().foo(), 4)
class TestABC(unittest.TestCase):
def test_ABC_helper(self):
# create an ABC using the helper class and perform basic checks
class C(abc.ABC):
@classmethod
@abc.abstractmethod
def foo(cls): return cls.__name__
self.assertEqual(type(C), abc.ABCMeta)
self.assertRaises(TypeError, C)
class D(C):
@classmethod
def foo(cls): return super().foo()
self.assertEqual(D.foo(), 'D')
def test_abstractmethod_basics(self):
@abc.abstractmethod
def foo(self): pass
self.assertTrue(foo.__isabstractmethod__)
def bar(self): pass
self.assertFalse(hasattr(bar, "__isabstractmethod__"))
def test_abstractproperty_basics(self):
@property
@abc.abstractmethod
def foo(self): pass
self.assertTrue(foo.__isabstractmethod__)
def bar(self): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc_ABCMeta):
@property
@abc.abstractmethod
def foo(self): return 3
self.assertRaises(TypeError, C)
class D(C):
@C.foo.getter
def foo(self): return super().foo
self.assertEqual(D().foo, 3)
def test_abstractclassmethod_basics(self):
@classmethod
@abc.abstractmethod
def foo(cls): pass
self.assertTrue(foo.__isabstractmethod__)
@classmethod
def bar(cls): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc_ABCMeta):
@classmethod
@abc.abstractmethod
def foo(cls): return cls.__name__
self.assertRaises(TypeError, C)
class D(C):
@classmethod
def foo(cls): return super().foo()
self.assertEqual(D.foo(), 'D')
self.assertEqual(D().foo(), 'D')
def test_abstractstaticmethod_basics(self):
@staticmethod
@abc.abstractmethod
def foo(): pass
self.assertTrue(foo.__isabstractmethod__)
@staticmethod
def bar(): pass
self.assertFalse(getattr(bar, "__isabstractmethod__", False))
class C(metaclass=abc_ABCMeta):
@staticmethod
@abc.abstractmethod
def foo(): return 3
self.assertRaises(TypeError, C)
class D(C):
@staticmethod
def foo(): return 4
self.assertEqual(D.foo(), 4)
self.assertEqual(D().foo(), 4)
def test_abstractmethod_integration(self):
for abstractthing in [abc.abstractmethod, abc.abstractproperty,
abc.abstractclassmethod,
abc.abstractstaticmethod]:
class C(metaclass=abc_ABCMeta):
@abstractthing
def foo(self): pass # abstract
def bar(self): pass # concrete
self.assertEqual(C.__abstractmethods__, {"foo"})
self.assertRaises(TypeError, C) # because foo is abstract
self.assertTrue(isabstract(C))
class D(C):
def bar(self): pass # concrete override of concrete
self.assertEqual(D.__abstractmethods__, {"foo"})
self.assertRaises(TypeError, D) # because foo is still abstract
self.assertTrue(isabstract(D))
class E(D):
def foo(self): pass
self.assertEqual(E.__abstractmethods__, set())
E() # now foo is concrete, too
self.assertFalse(isabstract(E))
class F(E):
@abstractthing
def bar(self): pass # abstract override of concrete
self.assertEqual(F.__abstractmethods__, {"bar"})
self.assertRaises(TypeError, F) # because bar is abstract now
self.assertTrue(isabstract(F))
def test_descriptors_with_abstractmethod(self):
class C(metaclass=abc_ABCMeta):
@property
@abc.abstractmethod
def foo(self): return 3
@foo.setter
@abc.abstractmethod
def foo(self, val): pass
self.assertRaises(TypeError, C)
class D(C):
@C.foo.getter
def foo(self): return super().foo
self.assertRaises(TypeError, D)
class E(D):
def foo(self): pass
self.assertEqual(E.__abstractmethods__, set())
E() # now foo is concrete, too
self.assertFalse(isabstract(E))
class F(E):
@abstractthing
def bar(self): pass # abstract override of concrete
self.assertEqual(F.__abstractmethods__, {"bar"})
self.assertRaises(TypeError, F) # because bar is abstract now
self.assertTrue(isabstract(F))
@D.foo.setter
def foo(self, val): pass
self.assertEqual(E().foo, 3)
# check that the property's __isabstractmethod__ descriptor does the
# right thing when presented with a value that fails truth testing:
class NotBool(object):
def __bool__(self):
raise ValueError()
__len__ = __bool__
with self.assertRaises(ValueError):
class F(C):
def bar(self):
pass
bar.__isabstractmethod__ = NotBool()
foo = property(bar)
def test_descriptors_with_abstractmethod(self):
class C(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def foo(self): return 3
@foo.setter
@abc.abstractmethod
def foo(self, val): pass
self.assertRaises(TypeError, C)
class D(C):
@C.foo.getter
def foo(self): return super().foo
self.assertRaises(TypeError, D)
class E(D):
@D.foo.setter
def foo(self, val): pass
self.assertEqual(E().foo, 3)
# check that the property's __isabstractmethod__ descriptor does the
# right thing when presented with a value that fails truth testing:
class NotBool(object):
def __bool__(self):
raise ValueError()
__len__ = __bool__
with self.assertRaises(ValueError):
class F(C):
def bar(self):
def test_customdescriptors_with_abstractmethod(self):
class Descriptor:
def __init__(self, fget, fset=None):
self._fget = fget
self._fset = fset
def getter(self, callable):
return Descriptor(callable, self._fget)
def setter(self, callable):
return Descriptor(self._fget, callable)
@property
def __isabstractmethod__(self):
return (getattr(self._fget, '__isabstractmethod__', False)
or getattr(self._fset, '__isabstractmethod__', False))
class C(metaclass=abc_ABCMeta):
@Descriptor
@abc.abstractmethod
def foo(self): return 3
@foo.setter
@abc.abstractmethod
def foo(self, val): pass
self.assertRaises(TypeError, C)
class D(C):
@C.foo.getter
def foo(self): return super().foo
self.assertRaises(TypeError, D)
class E(D):
@D.foo.setter
def foo(self, val): pass
self.assertFalse(E.foo.__isabstractmethod__)
def test_metaclass_abc(self):
# Metaclasses can be ABCs, too.
class A(metaclass=abc_ABCMeta):
@abc.abstractmethod
def x(self):
pass
bar.__isabstractmethod__ = NotBool()
foo = property(bar)
def test_customdescriptors_with_abstractmethod(self):
class Descriptor:
def __init__(self, fget, fset=None):
self._fget = fget
self._fset = fset
def getter(self, callable):
return Descriptor(callable, self._fget)
def setter(self, callable):
return Descriptor(self._fget, callable)
@property
def __isabstractmethod__(self):
return (getattr(self._fget, '__isabstractmethod__', False)
or getattr(self._fset, '__isabstractmethod__', False))
class C(metaclass=abc.ABCMeta):
@Descriptor
@abc.abstractmethod
def foo(self): return 3
@foo.setter
@abc.abstractmethod
def foo(self, val): pass
self.assertRaises(TypeError, C)
class D(C):
@C.foo.getter
def foo(self): return super().foo
self.assertRaises(TypeError, D)
class E(D):
@D.foo.setter
def foo(self, val): pass
self.assertFalse(E.foo.__isabstractmethod__)
def test_metaclass_abc(self):
# Metaclasses can be ABCs, too.
class A(metaclass=abc.ABCMeta):
@abc.abstractmethod
def x(self):
self.assertEqual(A.__abstractmethods__, {"x"})
class meta(type, A):
def x(self):
return 1
class C(metaclass=meta):
pass
self.assertEqual(A.__abstractmethods__, {"x"})
class meta(type, A):
def x(self):
return 1
class C(metaclass=meta):
pass
def test_registration_basics(self):
class A(metaclass=abc.ABCMeta):
pass
class B(object):
pass
b = B()
self.assertFalse(issubclass(B, A))
self.assertFalse(issubclass(B, (A,)))
self.assertNotIsInstance(b, A)
self.assertNotIsInstance(b, (A,))
B1 = A.register(B)
self.assertTrue(issubclass(B, A))
self.assertTrue(issubclass(B, (A,)))
self.assertIsInstance(b, A)
self.assertIsInstance(b, (A,))
self.assertIs(B1, B)
class C(B):
pass
c = C()
self.assertTrue(issubclass(C, A))
self.assertTrue(issubclass(C, (A,)))
self.assertIsInstance(c, A)
self.assertIsInstance(c, (A,))
def test_registration_basics(self):
class A(metaclass=abc_ABCMeta):
pass
class B(object):
pass
b = B()
self.assertFalse(issubclass(B, A))
self.assertFalse(issubclass(B, (A,)))
self.assertNotIsInstance(b, A)
self.assertNotIsInstance(b, (A,))
B1 = A.register(B)
self.assertTrue(issubclass(B, A))
self.assertTrue(issubclass(B, (A,)))
self.assertIsInstance(b, A)
self.assertIsInstance(b, (A,))
self.assertIs(B1, B)
class C(B):
pass
c = C()
self.assertTrue(issubclass(C, A))
self.assertTrue(issubclass(C, (A,)))
self.assertIsInstance(c, A)
self.assertIsInstance(c, (A,))
def test_register_as_class_deco(self):
class A(metaclass=abc.ABCMeta):
pass
@A.register
class B(object):
pass
b = B()
self.assertTrue(issubclass(B, A))
self.assertTrue(issubclass(B, (A,)))
self.assertIsInstance(b, A)
self.assertIsInstance(b, (A,))
@A.register
class C(B):
pass
c = C()
self.assertTrue(issubclass(C, A))
self.assertTrue(issubclass(C, (A,)))
self.assertIsInstance(c, A)
self.assertIsInstance(c, (A,))
self.assertIs(C, A.register(C))
def test_register_as_class_deco(self):
class A(metaclass=abc_ABCMeta):
pass
@A.register
class B(object):
pass
b = B()
self.assertTrue(issubclass(B, A))
self.assertTrue(issubclass(B, (A,)))
self.assertIsInstance(b, A)
self.assertIsInstance(b, (A,))
@A.register
class C(B):
pass
c = C()
self.assertTrue(issubclass(C, A))
self.assertTrue(issubclass(C, (A,)))
self.assertIsInstance(c, A)
self.assertIsInstance(c, (A,))
self.assertIs(C, A.register(C))
def test_isinstance_invalidation(self):
class A(metaclass=abc.ABCMeta):
pass
class B:
pass
b = B()
self.assertFalse(isinstance(b, A))
self.assertFalse(isinstance(b, (A,)))
token_old = abc.get_cache_token()
A.register(B)
token_new = abc.get_cache_token()
self.assertNotEqual(token_old, token_new)
self.assertTrue(isinstance(b, A))
self.assertTrue(isinstance(b, (A,)))
def test_isinstance_invalidation(self):
class A(metaclass=abc_ABCMeta):
pass
class B:
pass
b = B()
self.assertFalse(isinstance(b, A))
self.assertFalse(isinstance(b, (A,)))
token_old = abc_get_cache_token()
A.register(B)
token_new = abc_get_cache_token()
self.assertNotEqual(token_old, token_new)
self.assertTrue(isinstance(b, A))
self.assertTrue(isinstance(b, (A,)))
def test_registration_builtins(self):
class A(metaclass=abc.ABCMeta):
pass
A.register(int)
self.assertIsInstance(42, A)
self.assertIsInstance(42, (A,))
self.assertTrue(issubclass(int, A))
self.assertTrue(issubclass(int, (A,)))
class B(A):
pass
B.register(str)
class C(str): pass
self.assertIsInstance("", A)
self.assertIsInstance("", (A,))
self.assertTrue(issubclass(str, A))
self.assertTrue(issubclass(str, (A,)))
self.assertTrue(issubclass(C, A))
self.assertTrue(issubclass(C, (A,)))
def test_registration_builtins(self):
class A(metaclass=abc_ABCMeta):
pass
A.register(int)
self.assertIsInstance(42, A)
self.assertIsInstance(42, (A,))
self.assertTrue(issubclass(int, A))
self.assertTrue(issubclass(int, (A,)))
class B(A):
pass
B.register(str)
class C(str): pass
self.assertIsInstance("", A)
self.assertIsInstance("", (A,))
self.assertTrue(issubclass(str, A))
self.assertTrue(issubclass(str, (A,)))
self.assertTrue(issubclass(C, A))
self.assertTrue(issubclass(C, (A,)))
def test_registration_edge_cases(self):
class A(metaclass=abc.ABCMeta):
pass
A.register(A) # should pass silently
class A1(A):
pass
self.assertRaises(RuntimeError, A1.register, A) # cycles not allowed
class B(object):
pass
A1.register(B) # ok
A1.register(B) # should pass silently
class C(A):
pass
A.register(C) # should pass silently
self.assertRaises(RuntimeError, C.register, A) # cycles not allowed
C.register(B) # ok
def test_registration_edge_cases(self):
class A(metaclass=abc_ABCMeta):
pass
A.register(A) # should pass silently
class A1(A):
pass
self.assertRaises(RuntimeError, A1.register, A) # cycles not allowed
class B(object):
pass
A1.register(B) # ok
A1.register(B) # should pass silently
class C(A):
pass
A.register(C) # should pass silently
self.assertRaises(RuntimeError, C.register, A) # cycles not allowed
C.register(B) # ok
def test_register_non_class(self):
class A(metaclass=abc.ABCMeta):
pass
self.assertRaisesRegex(TypeError, "Can only register classes",
A.register, 4)
def test_register_non_class(self):
class A(metaclass=abc_ABCMeta):
pass
self.assertRaisesRegex(TypeError, "Can only register classes",
A.register, 4)
def test_registration_transitiveness(self):
class A(metaclass=abc.ABCMeta):
pass
self.assertTrue(issubclass(A, A))
self.assertTrue(issubclass(A, (A,)))
class B(metaclass=abc.ABCMeta):
pass
self.assertFalse(issubclass(A, B))
self.assertFalse(issubclass(A, (B,)))
self.assertFalse(issubclass(B, A))
self.assertFalse(issubclass(B, (A,)))
class C(metaclass=abc.ABCMeta):
pass
A.register(B)
class B1(B):
pass
self.assertTrue(issubclass(B1, A))
self.assertTrue(issubclass(B1, (A,)))
class C1(C):
pass
B1.register(C1)
self.assertFalse(issubclass(C, B))
self.assertFalse(issubclass(C, (B,)))
self.assertFalse(issubclass(C, B1))
self.assertFalse(issubclass(C, (B1,)))
self.assertTrue(issubclass(C1, A))
self.assertTrue(issubclass(C1, (A,)))
self.assertTrue(issubclass(C1, B))
self.assertTrue(issubclass(C1, (B,)))
self.assertTrue(issubclass(C1, B1))
self.assertTrue(issubclass(C1, (B1,)))
C1.register(int)
class MyInt(int):
pass
self.assertTrue(issubclass(MyInt, A))
self.assertTrue(issubclass(MyInt, (A,)))
self.assertIsInstance(42, A)
self.assertIsInstance(42, (A,))
def test_registration_transitiveness(self):
class A(metaclass=abc_ABCMeta):
pass
self.assertTrue(issubclass(A, A))
self.assertTrue(issubclass(A, (A,)))
class B(metaclass=abc_ABCMeta):
pass
self.assertFalse(issubclass(A, B))
self.assertFalse(issubclass(A, (B,)))
self.assertFalse(issubclass(B, A))
self.assertFalse(issubclass(B, (A,)))
class C(metaclass=abc_ABCMeta):
pass
A.register(B)
class B1(B):
pass
self.assertTrue(issubclass(B1, A))
self.assertTrue(issubclass(B1, (A,)))
class C1(C):
pass
B1.register(C1)
self.assertFalse(issubclass(C, B))
self.assertFalse(issubclass(C, (B,)))
self.assertFalse(issubclass(C, B1))
self.assertFalse(issubclass(C, (B1,)))
self.assertTrue(issubclass(C1, A))
self.assertTrue(issubclass(C1, (A,)))
self.assertTrue(issubclass(C1, B))
self.assertTrue(issubclass(C1, (B,)))
self.assertTrue(issubclass(C1, B1))
self.assertTrue(issubclass(C1, (B1,)))
C1.register(int)
class MyInt(int):
pass
self.assertTrue(issubclass(MyInt, A))
self.assertTrue(issubclass(MyInt, (A,)))
self.assertIsInstance(42, A)
self.assertIsInstance(42, (A,))
def test_all_new_methods_are_called(self):
class A(metaclass=abc.ABCMeta):
pass
class B(object):
counter = 0
def __new__(cls):
B.counter += 1
return super().__new__(cls)
class C(A, B):
pass
self.assertEqual(B.counter, 0)
C()
self.assertEqual(B.counter, 1)
def test_all_new_methods_are_called(self):
class A(metaclass=abc_ABCMeta):
pass
class B(object):
counter = 0
def __new__(cls):
B.counter += 1
return super().__new__(cls)
class C(A, B):
pass
self.assertEqual(B.counter, 0)
C()
self.assertEqual(B.counter, 1)
def test_ABC_has___slots__(self):
self.assertTrue(hasattr(abc.ABC, '__slots__'))
def test_ABC_has___slots__(self):
self.assertTrue(hasattr(abc.ABC, '__slots__'))
def test_tricky_new_works(self):
def with_metaclass(meta, *bases):
class metaclass(type):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
class A: ...
class B: ...
class C(with_metaclass(abc_ABCMeta, A, B)):
pass
self.assertEqual(C.__class__, abc_ABCMeta)
class TestABCWithInitSubclass(unittest.TestCase):
def test_works_with_init_subclass(self):
saved_kwargs = {}
class ReceivesClassKwargs:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__()
saved_kwargs.update(kwargs)
class Receiver(ReceivesClassKwargs, abc.ABC, x=1, y=2, z=3):
pass
self.assertEqual(saved_kwargs, dict(x=1, y=2, z=3))
class TestABCWithInitSubclass(unittest.TestCase):
def test_works_with_init_subclass(self):
class abc_ABC(metaclass=abc_ABCMeta):
__slots__ = ()
saved_kwargs = {}
class ReceivesClassKwargs:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__()
saved_kwargs.update(kwargs)
class Receiver(ReceivesClassKwargs, abc_ABC, x=1, y=2, z=3):
pass
self.assertEqual(saved_kwargs, dict(x=1, y=2, z=3))
return TestLegacyAPI, TestABC, TestABCWithInitSubclass
TestLegacyAPI_Py, TestABC_Py, TestABCWithInitSubclass_Py = test_factory(abc.ABCMeta,
abc.get_cache_token)
TestLegacyAPI_C, TestABC_C, TestABCWithInitSubclass_C = test_factory(_py_abc.ABCMeta,
_py_abc.get_cache_token)
if __name__ == "__main__":
unittest.main()

View File

@ -761,8 +761,8 @@ class GenericTests(BaseTestCase):
self.assertIsInstance(1, C)
C[int]
self.assertIsInstance(1, C)
C._abc_registry.clear()
C._abc_cache.clear() # To keep refleak hunting mode clean
C._abc_registry_clear()
C._abc_caches_clear() # To keep refleak hunting mode clean
def test_false_subclasses(self):
class MyMapping(MutableMapping[str, str]): pass

View File

@ -0,0 +1,10 @@
``_abc`` module is added. It is a speedup module with C implementations for
various functions and methods in ``abc``. Creating an ABC subclass and calling
``isinstance`` or ``issubclass`` with an ABC subclass are up to 1.5x faster.
In addition, this makes Python start-up up to 10% faster.
Note that the new implementation hides internal registry and caches, previously
accessible via private attributes ``_abc_registry``, ``_abc_cache``, and
``_abc_negative_cache``. There are three debugging helper methods that can be
used instead ``_dump_registry``, ``_abc_registry_clear``, and
``_abc_caches_clear``.

View File

@ -114,6 +114,7 @@ _weakref _weakref.c # weak references
_functools _functoolsmodule.c # Tools for working with functions and callable objects
_operator _operator.c # operator.add() and similar goodies
_collections _collectionsmodule.c # Container types
_abc _abc.c # Abstract base classes
itertools itertoolsmodule.c # Functions creating iterators for efficient looping
atexit atexitmodule.c # Register functions to be run at interpreter-shutdown
_signal signalmodule.c

822
Modules/_abc.c Normal file
View File

@ -0,0 +1,822 @@
/* ABCMeta implementation */
#include "Python.h"
#include "structmember.h"
#include "clinic/_abc.c.h"
/*[clinic input]
module _abc
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=964f5328e1aefcda]*/
PyDoc_STRVAR(_abc__doc__,
"Module contains faster C implementation of abc.ABCMeta");
_Py_IDENTIFIER(__abstractmethods__);
_Py_IDENTIFIER(__class__);
_Py_IDENTIFIER(__dict__);
_Py_IDENTIFIER(__bases__);
_Py_IDENTIFIER(_abc_impl);
_Py_IDENTIFIER(__subclasscheck__);
_Py_IDENTIFIER(__subclasshook__);
/* A global counter that is incremented each time a class is
registered as a virtual subclass of anything. It forces the
negative cache to be cleared before its next use.
Note: this counter is private. Use `abc.get_cache_token()` for
external code. */
static unsigned long long abc_invalidation_counter = 0;
/* This object stores internal state for ABCs.
Note that we can use normal sets for caches,
since they are never iterated over. */
typedef struct {
PyObject_HEAD
PyObject *_abc_registry;
PyObject *_abc_cache; /* Normal set of weak references. */
PyObject *_abc_negative_cache; /* Normal set of weak references. */
unsigned long long _abc_negative_cache_version;
} _abc_data;
static void
abc_data_dealloc(_abc_data *self)
{
Py_XDECREF(self->_abc_registry);
Py_XDECREF(self->_abc_cache);
Py_XDECREF(self->_abc_negative_cache);
Py_TYPE(self)->tp_free(self);
}
static PyObject *
abc_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
_abc_data *self = (_abc_data *) type->tp_alloc(type, 0);
if (self == NULL) {
return NULL;
}
self->_abc_registry = NULL;
self->_abc_cache = NULL;
self->_abc_negative_cache = NULL;
self->_abc_negative_cache_version = abc_invalidation_counter;
return (PyObject *) self;
}
PyDoc_STRVAR(abc_data_doc,
"Internal state held by ABC machinery.");
static PyTypeObject _abc_data_type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"_abc_data", /*tp_name*/
sizeof(_abc_data), /*tp_size*/
.tp_dealloc = (destructor)abc_data_dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_alloc = PyType_GenericAlloc,
.tp_new = abc_data_new,
};
static _abc_data *
_get_impl(PyObject *self)
{
PyObject *impl = _PyObject_GetAttrId(self, &PyId__abc_impl);
if (impl == NULL) {
return NULL;
}
if (Py_TYPE(impl) != &_abc_data_type) {
PyErr_SetString(PyExc_TypeError, "_abc_impl is set to a wrong type");
Py_DECREF(impl);
return NULL;
}
return (_abc_data *)impl;
}
static int
_in_weak_set(PyObject *set, PyObject *obj)
{
if (set == NULL || PySet_GET_SIZE(set) == 0) {
return 0;
}
PyObject *ref = PyWeakref_NewRef(obj, NULL);
if (ref == NULL) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_Clear();
return 0;
}
return -1;
}
int res = PySet_Contains(set, ref);
Py_DECREF(ref);
return res;
}
static PyObject *
_destroy(PyObject *setweakref, PyObject *objweakref)
{
PyObject *set;
set = PyWeakref_GET_OBJECT(setweakref);
if (set == Py_None) {
Py_RETURN_NONE;
}
Py_INCREF(set);
if (PySet_Discard(set, objweakref) < 0) {
Py_DECREF(set);
return NULL;
}
Py_DECREF(set);
Py_RETURN_NONE;
}
static PyMethodDef _destroy_def = {
"_destroy", (PyCFunction) _destroy, METH_O
};
static int
_add_to_weak_set(PyObject **pset, PyObject *obj)
{
if (*pset == NULL) {
*pset = PySet_New(NULL);
if (*pset == NULL) {
return -1;
}
}
PyObject *set = *pset;
PyObject *ref, *wr;
PyObject *destroy_cb;
wr = PyWeakref_NewRef(set, NULL);
if (wr == NULL) {
return -1;
}
destroy_cb = PyCFunction_NewEx(&_destroy_def, wr, NULL);
if (destroy_cb == NULL) {
Py_DECREF(wr);
return -1;
}
ref = PyWeakref_NewRef(obj, destroy_cb);
Py_DECREF(destroy_cb);
if (ref == NULL) {
Py_DECREF(wr);
return -1;
}
int ret = PySet_Add(set, ref);
Py_DECREF(wr);
Py_DECREF(ref);
return ret;
}
/*[clinic input]
_abc._reset_registry
self: object
/
Internal ABC helper to reset registry of a given class.
Should be only used by refleak.py
[clinic start generated code]*/
static PyObject *
_abc__reset_registry(PyObject *module, PyObject *self)
/*[clinic end generated code: output=92d591a43566cc10 input=12a0b7eb339ac35c]*/
{
_abc_data *impl = _get_impl(self);
if (impl == NULL) {
return NULL;
}
if (impl->_abc_registry != NULL && PySet_Clear(impl->_abc_registry) < 0) {
Py_DECREF(impl);
return NULL;
}
Py_DECREF(impl);
Py_RETURN_NONE;
}
/*[clinic input]
_abc._reset_caches
self: object
/
Internal ABC helper to reset both caches of a given class.
Should be only used by refleak.py
[clinic start generated code]*/
static PyObject *
_abc__reset_caches(PyObject *module, PyObject *self)
/*[clinic end generated code: output=f296f0d5c513f80c input=c0ac616fd8acfb6f]*/
{
_abc_data *impl = _get_impl(self);
if (impl == NULL) {
return NULL;
}
if (impl->_abc_cache != NULL && PySet_Clear(impl->_abc_cache) < 0) {
Py_DECREF(impl);
return NULL;
}
/* also the second cache */
if (impl->_abc_negative_cache != NULL &&
PySet_Clear(impl->_abc_negative_cache) < 0) {
Py_DECREF(impl);
return NULL;
}
Py_DECREF(impl);
Py_RETURN_NONE;
}
/*[clinic input]
_abc._get_dump
self: object
/
Internal ABC helper for cache and registry debugging.
Return shallow copies of registry, of both caches, and
negative cache version. Don't call this function directly,
instead use ABC._dump_registry() for a nice repr.
[clinic start generated code]*/
static PyObject *
_abc__get_dump(PyObject *module, PyObject *self)
/*[clinic end generated code: output=9d9569a8e2c1c443 input=2c5deb1bfe9e3c79]*/
{
_abc_data *impl = _get_impl(self);
if (impl == NULL) {
return NULL;
}
PyObject *res = Py_BuildValue("NNNK",
PySet_New(impl->_abc_registry),
PySet_New(impl->_abc_cache),
PySet_New(impl->_abc_negative_cache),
impl->_abc_negative_cache_version);
Py_DECREF(impl);
return res;
}
// Compute set of abstract method names.
static int
compute_abstract_methods(PyObject *self)
{
int ret = -1;
PyObject *abstracts = PyFrozenSet_New(NULL);
if (abstracts == NULL) {
return -1;
}
PyObject *ns = NULL, *items = NULL, *bases = NULL; // Py_XDECREF()ed on error.
/* Stage 1: direct abstract methods. */
ns = _PyObject_GetAttrId(self, &PyId___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);
if (!items) {
goto error;
}
assert(PyList_Check(items));
for (Py_ssize_t pos = 0; pos < PyList_GET_SIZE(items); pos++) {
PyObject *it = PySequence_Fast(
PyList_GET_ITEM(items, pos),
"items() returned non-iterable");
if (!it) {
goto error;
}
if (PySequence_Fast_GET_SIZE(it) != 2) {
PyErr_SetString(PyExc_TypeError,
"items() returned item which size is not 2");
Py_DECREF(it);
goto error;
}
// borrowed
PyObject *key = PySequence_Fast_GET_ITEM(it, 0);
PyObject *value = PySequence_Fast_GET_ITEM(it, 1);
// items or it may be cleared while accessing __abstractmethod__
// So we need to keep strong reference for key
Py_INCREF(key);
int is_abstract = _PyObject_IsAbstract(value);
if (is_abstract < 0 ||
(is_abstract && PySet_Add(abstracts, key) < 0)) {
Py_DECREF(it);
Py_DECREF(key);
goto error;
}
Py_DECREF(key);
Py_DECREF(it);
}
/* Stage 2: inherited abstract methods. */
bases = _PyObject_GetAttrId(self, &PyId___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;
if (_PyObject_LookupAttrId(item, &PyId___abstractmethods__,
&base_abstracts) < 0) {
goto error;
}
if (base_abstracts == NULL) {
continue;
}
if (!(iter = PyObject_GetIter(base_abstracts))) {
Py_DECREF(base_abstracts);
goto error;
}
Py_DECREF(base_abstracts);
PyObject *key, *value;
while ((key = PyIter_Next(iter))) {
if (_PyObject_LookupAttr(self, key, &value) < 0) {
Py_DECREF(key);
Py_DECREF(iter);
goto error;
}
if (value == NULL) {
Py_DECREF(key);
continue;
}
int is_abstract = _PyObject_IsAbstract(value);
Py_DECREF(value);
if (is_abstract < 0 ||
(is_abstract && PySet_Add(abstracts, key) < 0))
{
Py_DECREF(key);
Py_DECREF(iter);
goto error;
}
Py_DECREF(key);
}
Py_DECREF(iter);
if (PyErr_Occurred()) {
goto error;
}
}
if (_PyObject_SetAttrId(self, &PyId___abstractmethods__, abstracts) < 0) {
goto error;
}
ret = 0;
error:
Py_DECREF(abstracts);
Py_XDECREF(ns);
Py_XDECREF(items);
Py_XDECREF(bases);
return ret;
}
/*[clinic input]
_abc._abc_init
self: object
/
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=8d7fe470ff77f029]*/
{
PyObject *data;
if (compute_abstract_methods(self) < 0) {
return NULL;
}
/* Set up inheritance registry. */
data = abc_data_new(&_abc_data_type, NULL, NULL);
if (data == NULL) {
return NULL;
}
if (_PyObject_SetAttrId(self, &PyId__abc_impl, data) < 0) {
Py_DECREF(data);
return NULL;
}
Py_DECREF(data);
Py_RETURN_NONE;
}
/*[clinic input]
_abc._abc_register
self: object
subclass: object
/
Internal ABC helper for subclasss registration. Should be never used outside abc module.
[clinic start generated code]*/
static PyObject *
_abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass)
/*[clinic end generated code: output=7851e7668c963524 input=ca589f8c3080e67f]*/
{
if (!PyType_Check(subclass)) {
PyErr_SetString(PyExc_TypeError, "Can only register classes");
return NULL;
}
int result = PyObject_IsSubclass(subclass, self);
if (result > 0) {
Py_INCREF(subclass);
return subclass; /* Already a subclass. */
}
if (result < 0) {
return NULL;
}
/* Subtle: test for cycles *after* testing for "already a subclass";
this means we allow X.register(X) and interpret it as a no-op. */
result = PyObject_IsSubclass(self, subclass);
if (result > 0) {
/* This would create a cycle, which is bad for the algorithm below. */
PyErr_SetString(PyExc_RuntimeError, "Refusing to create an inheritance cycle");
return NULL;
}
if (result < 0) {
return NULL;
}
_abc_data *impl = _get_impl(self);
if (impl == NULL) {
return NULL;
}
if (_add_to_weak_set(&impl->_abc_registry, subclass) < 0) {
Py_DECREF(impl);
return NULL;
}
Py_DECREF(impl);
/* Invalidate negative cache */
abc_invalidation_counter++;
Py_INCREF(subclass);
return subclass;
}
/*[clinic input]
_abc._abc_instancecheck
self: object
instance: object
/
Internal ABC helper for instance checks. Should be never used outside abc module.
[clinic start generated code]*/
static PyObject *
_abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
PyObject *instance)
/*[clinic end generated code: output=b8b5148f63b6b56f input=a4f4525679261084]*/
{
PyObject *subtype, *result = NULL, *subclass = NULL;
_abc_data *impl = _get_impl(self);
if (impl == NULL) {
return NULL;
}
subclass = _PyObject_GetAttrId(instance, &PyId___class__);
if (subclass == NULL) {
Py_DECREF(impl);
return NULL;
}
/* Inline the cache checking. */
int incache = _in_weak_set(impl->_abc_cache, subclass);
if (incache < 0) {
goto end;
}
if (incache > 0) {
result = Py_True;
Py_INCREF(result);
goto end;
}
subtype = (PyObject *)Py_TYPE(instance);
if (subtype == subclass) {
if (impl->_abc_negative_cache_version == abc_invalidation_counter) {
incache = _in_weak_set(impl->_abc_negative_cache, subclass);
if (incache < 0) {
goto end;
}
if (incache > 0) {
result = Py_False;
Py_INCREF(result);
goto end;
}
}
/* Fall back to the subclass check. */
result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__,
subclass, NULL);
goto end;
}
result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__,
subclass, NULL);
if (result == NULL) {
goto end;
}
switch (PyObject_IsTrue(result)) {
case -1:
Py_DECREF(result);
result = NULL;
break;
case 0:
Py_DECREF(result);
result = _PyObject_CallMethodIdObjArgs(self, &PyId___subclasscheck__,
subtype, NULL);
break;
case 1: // Nothing to do.
break;
default:
Py_UNREACHABLE();
}
end:
Py_XDECREF(impl);
Py_XDECREF(subclass);
return result;
}
// Return -1 when exception occured.
// Return 1 when result is set.
// Return 0 otherwise.
static int subclasscheck_check_registry(_abc_data *impl, PyObject *subclass,
PyObject **result);
/*[clinic input]
_abc._abc_subclasscheck
self: object
subclass: object
/
Internal ABC helper for subclasss checks. Should be never used outside abc module.
[clinic start generated code]*/
static PyObject *
_abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
PyObject *subclass)
/*[clinic end generated code: output=b56c9e4a530e3894 input=1d947243409d10b8]*/
{
PyObject *ok, *mro, *subclasses = NULL, *result = NULL;
Py_ssize_t pos;
int incache;
_abc_data *impl = _get_impl(self);
if (impl == NULL) {
return NULL;
}
/* 1. Check cache. */
incache = _in_weak_set(impl->_abc_cache, subclass);
if (incache < 0) {
goto end;
}
if (incache > 0) {
result = Py_True;
goto end;
}
/* 2. Check negative cache; may have to invalidate. */
if (impl->_abc_negative_cache_version < abc_invalidation_counter) {
/* Invalidate the negative cache. */
if (impl->_abc_negative_cache != NULL &&
PySet_Clear(impl->_abc_negative_cache) < 0)
{
goto end;
}
impl->_abc_negative_cache_version = abc_invalidation_counter;
}
else {
incache = _in_weak_set(impl->_abc_negative_cache, subclass);
if (incache < 0) {
goto end;
}
if (incache > 0) {
result = Py_False;
goto end;
}
}
/* 3. Check the subclass hook. */
ok = _PyObject_CallMethodIdObjArgs((PyObject *)self, &PyId___subclasshook__,
subclass, NULL);
if (ok == NULL) {
goto end;
}
if (ok == Py_True) {
Py_DECREF(ok);
if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {
goto end;
}
result = Py_True;
goto end;
}
if (ok == Py_False) {
Py_DECREF(ok);
if (_add_to_weak_set(&impl->_abc_negative_cache, subclass) < 0) {
goto end;
}
result = Py_False;
goto end;
}
if (ok != Py_NotImplemented) {
Py_DECREF(ok);
PyErr_SetString(PyExc_AssertionError, "__subclasshook__ must return either"
" False, True, or NotImplemented");
goto end;
}
Py_DECREF(ok);
/* 4. Check if it's a direct subclass. */
mro = ((PyTypeObject *)subclass)->tp_mro;
assert(PyTuple_Check(mro));
for (pos = 0; pos < PyTuple_GET_SIZE(mro); pos++) {
PyObject *mro_item = PyTuple_GET_ITEM(mro, pos);
if (mro_item == NULL) {
goto end;
}
if ((PyObject *)self == mro_item) {
if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {
goto end;
}
result = Py_True;
goto end;
}
}
/* 5. Check if it's a subclass of a registered class (recursive). */
if (subclasscheck_check_registry(impl, subclass, &result)) {
// Exception occured or result is set.
goto end;
}
/* 6. Check if it's a subclass of a subclass (recursive). */
subclasses = PyObject_CallMethod(self, "__subclasses__", NULL);
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_GET_ITEM(subclasses, pos);
Py_INCREF(scls);
int r = PyObject_IsSubclass(subclass, scls);
Py_DECREF(scls);
if (r > 0) {
if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {
goto end;
}
result = Py_True;
goto end;
}
if (r < 0) {
goto end;
}
}
/* No dice; update negative cache. */
if (_add_to_weak_set(&impl->_abc_negative_cache, subclass) < 0) {
goto end;
}
result = Py_False;
end:
Py_XDECREF(impl);
Py_XDECREF(subclasses);
Py_XINCREF(result);
return result;
}
static int
subclasscheck_check_registry(_abc_data *impl, PyObject *subclass,
PyObject **result)
{
// Fast path: check subclass is in weakref directly.
int ret = _in_weak_set(impl->_abc_registry, subclass);
if (ret < 0) {
*result = NULL;
return -1;
}
if (ret > 0) {
*result = Py_True;
return 1;
}
if (impl->_abc_registry == NULL) {
return 0;
}
Py_ssize_t registry_size = PySet_Size(impl->_abc_registry);
if (registry_size == 0) {
return 0;
}
// Weakref callback may remove entry from set.
// So we take snapshot of registry first.
PyObject **copy = PyMem_Malloc(sizeof(PyObject*) * registry_size);
PyObject *key;
Py_ssize_t pos = 0;
Py_hash_t hash;
Py_ssize_t i = 0;
while (_PySet_NextEntry(impl->_abc_registry, &pos, &key, &hash)) {
Py_INCREF(key);
copy[i++] = key;
}
assert(i == registry_size);
for (i = 0; i < registry_size; i++) {
PyObject *rkey = PyWeakref_GetObject(copy[i]);
if (rkey == NULL) {
// Someone inject non-weakref type in the registry.
ret = -1;
break;
}
if (rkey == Py_None) {
continue;
}
Py_INCREF(rkey);
int r = PyObject_IsSubclass(subclass, rkey);
Py_DECREF(rkey);
if (r < 0) {
ret = -1;
break;
}
if (r > 0) {
if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {
ret = -1;
break;
}
*result = Py_True;
ret = 1;
break;
}
}
for (i = 0; i < registry_size; i++) {
Py_DECREF(copy[i]);
}
PyMem_Free(copy);
return ret;
}
/*[clinic input]
_abc.get_cache_token
Returns the current ABC cache token.
The token is an opaque object (supporting equality testing) identifying the
current version of the ABC cache for virtual subclasses. The token changes
with every call to register() on any ABC.
[clinic start generated code]*/
static PyObject *
_abc_get_cache_token_impl(PyObject *module)
/*[clinic end generated code: output=c7d87841e033dacc input=70413d1c423ad9f9]*/
{
return PyLong_FromUnsignedLongLong(abc_invalidation_counter);
}
static struct PyMethodDef module_functions[] = {
_ABC_GET_CACHE_TOKEN_METHODDEF
_ABC__ABC_INIT_METHODDEF
_ABC__RESET_REGISTRY_METHODDEF
_ABC__RESET_CACHES_METHODDEF
_ABC__GET_DUMP_METHODDEF
_ABC__ABC_REGISTER_METHODDEF
_ABC__ABC_INSTANCECHECK_METHODDEF
_ABC__ABC_SUBCLASSCHECK_METHODDEF
{NULL, NULL} /* sentinel */
};
static struct PyModuleDef _abcmodule = {
PyModuleDef_HEAD_INIT,
"_abc",
_abc__doc__,
-1,
module_functions,
NULL,
NULL,
NULL,
NULL
};
PyMODINIT_FUNC
PyInit__abc(void)
{
if (PyType_Ready(&_abc_data_type) < 0) {
return NULL;
}
_abc_data_type.tp_doc = abc_data_doc;
return PyModule_Create(&_abcmodule);
}

162
Modules/clinic/_abc.c.h Normal file
View File

@ -0,0 +1,162 @@
/*[clinic input]
preserve
[clinic start generated code]*/
PyDoc_STRVAR(_abc__reset_registry__doc__,
"_reset_registry($module, self, /)\n"
"--\n"
"\n"
"Internal ABC helper to reset registry of a given class.\n"
"\n"
"Should be only used by refleak.py");
#define _ABC__RESET_REGISTRY_METHODDEF \
{"_reset_registry", (PyCFunction)_abc__reset_registry, METH_O, _abc__reset_registry__doc__},
PyDoc_STRVAR(_abc__reset_caches__doc__,
"_reset_caches($module, self, /)\n"
"--\n"
"\n"
"Internal ABC helper to reset both caches of a given class.\n"
"\n"
"Should be only used by refleak.py");
#define _ABC__RESET_CACHES_METHODDEF \
{"_reset_caches", (PyCFunction)_abc__reset_caches, METH_O, _abc__reset_caches__doc__},
PyDoc_STRVAR(_abc__get_dump__doc__,
"_get_dump($module, self, /)\n"
"--\n"
"\n"
"Internal ABC helper for cache and registry debugging.\n"
"\n"
"Return shallow copies of registry, of both caches, and\n"
"negative cache version. Don\'t call this function directly,\n"
"instead use ABC._dump_registry() for a nice repr.");
#define _ABC__GET_DUMP_METHODDEF \
{"_get_dump", (PyCFunction)_abc__get_dump, METH_O, _abc__get_dump__doc__},
PyDoc_STRVAR(_abc__abc_init__doc__,
"_abc_init($module, self, /)\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__},
PyDoc_STRVAR(_abc__abc_register__doc__,
"_abc_register($module, self, subclass, /)\n"
"--\n"
"\n"
"Internal ABC helper for subclasss registration. Should be never used outside abc module.");
#define _ABC__ABC_REGISTER_METHODDEF \
{"_abc_register", (PyCFunction)_abc__abc_register, METH_FASTCALL, _abc__abc_register__doc__},
static PyObject *
_abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass);
static PyObject *
_abc__abc_register(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
PyObject *self;
PyObject *subclass;
if (!_PyArg_UnpackStack(args, nargs, "_abc_register",
2, 2,
&self, &subclass)) {
goto exit;
}
return_value = _abc__abc_register_impl(module, self, subclass);
exit:
return return_value;
}
PyDoc_STRVAR(_abc__abc_instancecheck__doc__,
"_abc_instancecheck($module, self, instance, /)\n"
"--\n"
"\n"
"Internal ABC helper for instance checks. Should be never used outside abc module.");
#define _ABC__ABC_INSTANCECHECK_METHODDEF \
{"_abc_instancecheck", (PyCFunction)_abc__abc_instancecheck, METH_FASTCALL, _abc__abc_instancecheck__doc__},
static PyObject *
_abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
PyObject *instance);
static PyObject *
_abc__abc_instancecheck(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
PyObject *self;
PyObject *instance;
if (!_PyArg_UnpackStack(args, nargs, "_abc_instancecheck",
2, 2,
&self, &instance)) {
goto exit;
}
return_value = _abc__abc_instancecheck_impl(module, self, instance);
exit:
return return_value;
}
PyDoc_STRVAR(_abc__abc_subclasscheck__doc__,
"_abc_subclasscheck($module, self, subclass, /)\n"
"--\n"
"\n"
"Internal ABC helper for subclasss checks. Should be never used outside abc module.");
#define _ABC__ABC_SUBCLASSCHECK_METHODDEF \
{"_abc_subclasscheck", (PyCFunction)_abc__abc_subclasscheck, METH_FASTCALL, _abc__abc_subclasscheck__doc__},
static PyObject *
_abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
PyObject *subclass);
static PyObject *
_abc__abc_subclasscheck(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
PyObject *self;
PyObject *subclass;
if (!_PyArg_UnpackStack(args, nargs, "_abc_subclasscheck",
2, 2,
&self, &subclass)) {
goto exit;
}
return_value = _abc__abc_subclasscheck_impl(module, self, subclass);
exit:
return return_value;
}
PyDoc_STRVAR(_abc_get_cache_token__doc__,
"get_cache_token($module, /)\n"
"--\n"
"\n"
"Returns the current ABC cache token.\n"
"\n"
"The token is an opaque object (supporting equality testing) identifying the\n"
"current version of the ABC cache for virtual subclasses. The token changes\n"
"with every call to register() on any ABC.");
#define _ABC_GET_CACHE_TOKEN_METHODDEF \
{"get_cache_token", (PyCFunction)_abc_get_cache_token, METH_NOARGS, _abc_get_cache_token__doc__},
static PyObject *
_abc_get_cache_token_impl(PyObject *module);
static PyObject *
_abc_get_cache_token(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return _abc_get_cache_token_impl(module);
}
/*[clinic end generated code: output=9d6f861a8f45bc6f input=a9049054013a1b77]*/

View File

@ -5,6 +5,7 @@
#include "Python.h"
extern PyObject* PyInit__abc(void);
extern PyObject* PyInit_array(void);
extern PyObject* PyInit_audioop(void);
extern PyObject* PyInit_binascii(void);
@ -80,6 +81,7 @@ extern PyObject* PyInit__imp(void);
struct _inittab _PyImport_Inittab[] = {
{"_abc", PyInit__abc},
{"array", PyInit_array},
{"_ast", PyInit__ast},
{"audioop", PyInit_audioop},

View File

@ -228,6 +228,7 @@
<ClInclude Include="$(zlibDir)\zutil.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\Modules\_abc.c" />
<ClCompile Include="..\Modules\_asynciomodule.c" />
<ClCompile Include="..\Modules\_bisectmodule.c" />
<ClCompile Include="..\Modules\_blake2\blake2module.c" />

View File

@ -473,6 +473,9 @@
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\Modules\_abc.c">
<Filter>Modules</Filter>
</ClCompile>
<ClCompile Include="..\Modules\_bisectmodule.c">
<Filter>Modules</Filter>
</ClCompile>

View File

@ -712,6 +712,8 @@ class PyBuildExt(build_ext):
exts.append( Extension('_opcode', ['_opcode.c']) )
# asyncio speedups
exts.append( Extension("_asyncio", ["_asynciomodule.c"]) )
# _abc speedups
exts.append( Extension("_abc", ["_abc.c"]) )
# _queue module
exts.append( Extension("_queue", ["_queuemodule.c"]) )