[pypy-commit] pypy py3.7: Merged in Yannick_Jadoul/pypy/py3.7-bpo-31333 (pull request #675)
rlamy
pypy.commits at gmail.com
Thu Oct 31 13:42:06 EDT 2019
Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: py3.7
Changeset: r97918:4a29e3fa9b40
Date: 2019-10-31 17:41 +0000
http://bitbucket.org/pypy/pypy/changeset/4a29e3fa9b40/
Log: Merged in Yannick_Jadoul/pypy/py3.7-bpo-31333 (pull request #675)
bpo-31333: Adding built-in module _abc
diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py
--- a/pypy/config/pypyoption.py
+++ b/pypy/config/pypyoption.py
@@ -27,7 +27,7 @@
"_codecs", "atexit", "gc", "_weakref", "marshal", "errno", "imp",
"itertools", "math", "cmath", "_sre", "_pickle_support",
"parser", "symbol", "token", "_ast", "_random", "__pypy__",
- "_string", "_testing", "time"
+ "_string", "_testing", "time", "_abc",
])
diff --git a/pypy/module/_abc/__init__.py b/pypy/module/_abc/__init__.py
new file mode 100644
diff --git a/pypy/module/_abc/app_abc.py b/pypy/module/_abc/app_abc.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_abc/app_abc.py
@@ -0,0 +1,175 @@
+# NOT_RPYTHON
+"""
+Plain Python definition of the builtin ABC-related functions.
+"""
+
+# Cannot use _weakrefset like in _py_abc.py, since it is not a built-in module
+from _weakref import ref
+
+# We'll make our own WeakSet instead, with only the functionality that's needed
+# This replaces the easily-forgetable calls to '_add_to_weak_set' and
+# '_in_weak_set' from the CPython implementation
+class SimpleWeakSet:
+ def __init__(self, data=None):
+ self.data = set()
+ def _remove(item, selfref=ref(self)):
+ self = selfref()
+ if self is not None:
+ self.data.discard(item)
+ self._remove = _remove
+
+ def __iter__(self):
+ # Weakref callback may remove entry from set.
+ # So we make a copy first.
+ copy = list(self.data)
+ for itemref in copy:
+ item = itemref()
+ yield item
+
+ def __contains__(self, item):
+ try:
+ wr = ref(item)
+ except TypeError:
+ return False
+ return wr in self.data
+
+ def add(self, item):
+ self.data.add(ref(item, self._remove))
+
+ def clear(self):
+ self.data.clear()
+
+
+abc_invalidation_counter = 0
+
+
+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 abc_invalidation_counter
+
+
+def _abc_init(cls):
+ """Internal ABC helper for class set-up. Should be never used outside abc module."""
+ namespace = cls.__dict__
+ abstracts = {name
+ for name, value in namespace.items()
+ if getattr(value, "__isabstractmethod__", False)}
+ bases = cls.__bases__
+ 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 = SimpleWeakSet()
+ cls._abc_cache = SimpleWeakSet()
+ cls._abc_negative_cache = SimpleWeakSet()
+ cls._abc_negative_cache_version = abc_invalidation_counter
+
+
+def _abc_register(cls, subclass):
+ """Internal ABC helper for subclasss registration. Should be never used outside abc module."""
+ 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)
+ global abc_invalidation_counter
+ abc_invalidation_counter += 1 # Invalidate negative cache
+ return subclass
+
+
+def _abc_instancecheck(cls, instance):
+ """Internal ABC helper for instance checks. Should be never used outside abc module."""
+ # 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 == 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 _abc_subclasscheck(cls, subclass):
+ """Internal ABC helper for subclasss checks. Should be never used outside abc module."""
+ if not isinstance(subclass, type):
+ raise TypeError('issubclass() arg 1 must be a class')
+ # Check cache
+ if subclass in cls._abc_cache:
+ return True
+ # Check negative cache; may have to invalidate
+ if cls._abc_negative_cache_version < abc_invalidation_counter:
+ # Invalidate the negative cache
+ cls._abc_negative_cache = SimpleWeakSet()
+ cls._abc_negative_cache_version = 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 _get_dump(cls):
+ """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."""
+ return (cls._abc_registry.data,
+ cls._abc_cache.data,
+ cls._abc_negative_cache.data,
+ cls._abc_negative_cache_version)
+
+
+def _reset_registry(cls):
+ """Internal ABC helper to reset registry of a given class.
+
+ Should be only used by refleak.py"""
+ cls._abc_registry.clear()
+
+
+def _reset_caches(cls):
+ """Internal ABC helper to reset both caches of a given class.
+
+ Should be only used by refleak.py"""
+ cls._abc_cache.clear()
+ cls._abc_negative_cache.clear()
diff --git a/pypy/module/_abc/moduledef.py b/pypy/module/_abc/moduledef.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_abc/moduledef.py
@@ -0,0 +1,17 @@
+from pypy.interpreter.mixedmodule import MixedModule
+
+class Module(MixedModule):
+
+ appleveldefs = {
+ "get_cache_token": "app_abc.get_cache_token",
+ "_abc_init": "app_abc._abc_init",
+ "_abc_register": "app_abc._abc_register",
+ "_abc_instancecheck": "app_abc._abc_instancecheck",
+ "_abc_subclasscheck": "app_abc._abc_subclasscheck",
+ "_get_dump": "app_abc._get_dump",
+ "_reset_registry": "app_abc._reset_registry",
+ "_reset_caches": "app_abc._reset_caches",
+ }
+
+ interpleveldefs = {
+ }
diff --git a/pypy/module/_abc/test/test_abc.py b/pypy/module/_abc/test/test_abc.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_abc/test/test_abc.py
@@ -0,0 +1,84 @@
+class AppTestAbcModule:
+ def test_import_builtin(self):
+ from _abc import (get_cache_token, _abc_init, _abc_register,
+ _abc_instancecheck, _abc_subclasscheck, _get_dump,
+ _reset_registry, _reset_caches)
+
+ def test_basic(self):
+ import _abc
+
+ class SomeABC: pass
+
+ _abc._abc_init(SomeABC)
+ assert hasattr(SomeABC, '__abstractmethods__')
+
+ class SomeConcreteSubClass: pass
+ _abc._abc_register(SomeABC, SomeConcreteSubClass)
+
+ # _abc._abc_instancecheck calls cls.__subclasscheck__, but since
+ # we've only called _abc_init and haven't set the metaclass, we
+ # need to monkeypatch SomeABC before calling _abc_instancecheck
+ from types import MethodType
+ SomeABC.__subclasscheck__ = MethodType(_abc._abc_subclasscheck, SomeABC)
+ assert _abc._abc_instancecheck(SomeABC, SomeConcreteSubClass())
+ assert _abc._abc_subclasscheck(SomeABC, SomeConcreteSubClass)
+
+ class SomeOtherClass: pass
+ assert not _abc._abc_instancecheck(SomeABC, SomeOtherClass())
+ assert not _abc._abc_subclasscheck(SomeABC, SomeOtherClass)
+
+ _abc._reset_registry(SomeABC)
+ _abc._reset_caches(SomeABC)
+ assert not _abc._abc_instancecheck(SomeABC, SomeConcreteSubClass())
+ assert not _abc._abc_subclasscheck(SomeABC, SomeConcreteSubClass)
+
+ def test_cache(self):
+ import _abc
+
+ class SomeABC:
+ pass
+
+ token_before = _abc.get_cache_token()
+ assert _abc.get_cache_token() == token_before
+
+ _abc._abc_init(SomeABC)
+ assert _abc.get_cache_token() == token_before
+
+ class SomeConcreteSubClass: pass
+ _abc._abc_register(SomeABC, SomeConcreteSubClass)
+ assert _abc.get_cache_token() != token_before
+
+ registry, cache, negative_cache, negative_cache_version = _abc._get_dump(SomeABC)
+ assert len(registry) == 1
+ assert len(cache) == 0
+ assert len(negative_cache) == 0
+ assert negative_cache_version == token_before
+
+ assert _abc._abc_subclasscheck(SomeABC, SomeConcreteSubClass)
+ registry, cache, negative_cache, negative_cache_version = _abc._get_dump(SomeABC)
+ assert len(registry) == 1
+ assert len(cache) == 1
+ assert len(negative_cache) == 0
+ assert negative_cache_version == _abc.get_cache_token()
+
+ class SomeOtherClass: pass
+ assert not _abc._abc_subclasscheck(SomeABC, SomeOtherClass)
+ registry, cache, negative_cache, negative_cache_version = _abc._get_dump(SomeABC)
+ assert len(registry) == 1
+ assert len(cache) == 1
+ assert len(negative_cache) == 1
+ assert negative_cache_version == _abc.get_cache_token()
+
+ _abc._reset_caches(SomeABC)
+ registry, cache, negative_cache, negative_cache_version = _abc._get_dump(SomeABC)
+ assert len(registry) == 1
+ assert len(cache) == 0
+ assert len(negative_cache) == 0
+ assert negative_cache_version == _abc.get_cache_token()
+
+ _abc._reset_registry(SomeABC)
+ registry, cache, negative_cache, negative_cache_version = _abc._get_dump(SomeABC)
+ assert len(registry) == 0
+ assert len(cache) == 0
+ assert len(negative_cache) == 0
+ assert negative_cache_version == _abc.get_cache_token()
More information about the pypy-commit
mailing list