[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