diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 7c98c7cfa38..c35f07c0eab 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -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 ======================= diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py new file mode 100644 index 00000000000..6f42ef32fa6 --- /dev/null +++ b/Lib/_py_abc.py @@ -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 diff --git a/Lib/abc.py b/Lib/abc.py index 9bdc36dce65..7094141277a 100644 --- a/Lib/abc.py +++ b/Lib/abc.py @@ -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 diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 2ca9aa87644..6724488fcfb 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -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() diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 61c2876708c..af26c1d6b8c 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -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() diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3f24faf3762..f56caa13a29 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -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 diff --git a/Misc/NEWS.d/next/Library/2018-02-15-08-18-52.bpo-31333.4fF-gM.rst b/Misc/NEWS.d/next/Library/2018-02-15-08-18-52.bpo-31333.4fF-gM.rst new file mode 100644 index 00000000000..63fc72a5c11 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-02-15-08-18-52.bpo-31333.4fF-gM.rst @@ -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``. diff --git a/Modules/Setup.dist b/Modules/Setup.dist index 4b463b28d67..a833774a153 100644 --- a/Modules/Setup.dist +++ b/Modules/Setup.dist @@ -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 diff --git a/Modules/_abc.c b/Modules/_abc.c new file mode 100644 index 00000000000..504e23d9a74 --- /dev/null +++ b/Modules/_abc.c @@ -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); +} diff --git a/Modules/clinic/_abc.c.h b/Modules/clinic/_abc.c.h new file mode 100644 index 00000000000..b1ec371d155 --- /dev/null +++ b/Modules/clinic/_abc.c.h @@ -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]*/ diff --git a/PC/config.c b/PC/config.c index 0c08bfeaa0d..568a0fa9a46 100644 --- a/PC/config.c +++ b/PC/config.c @@ -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}, diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index bf4131e91b0..90330faa0cf 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -228,6 +228,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 5f980388e5f..b51fd54f8b4 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -473,6 +473,9 @@ + + Modules + Modules diff --git a/setup.py b/setup.py index 6f9a39719b4..f4f6e4fdb1f 100644 --- a/setup.py +++ b/setup.py @@ -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"]) )