[pypy-commit] pypy default: Merged in py2-mappingproxy (pull request #470)

rlamy pypy.commits at gmail.com
Tue Aug 9 19:32:54 EDT 2016


Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: 
Changeset: r86124:1900672c0757
Date: 2016-08-10 00:31 +0100
http://bitbucket.org/pypy/pypy/changeset/1900672c0757/

Log:	Merged in py2-mappingproxy (pull request #470)

	Fix the dictproxy type to behave as in CPython. App-level
	cls.__dict__ and C-level cls->tp_dict now return different objects
	with the former being an opaque (at app-level) wrapper around the
	latter.

diff --git a/pypy/module/cppyy/pythonify.py b/pypy/module/cppyy/pythonify.py
--- a/pypy/module/cppyy/pythonify.py
+++ b/pypy/module/cppyy/pythonify.py
@@ -175,7 +175,7 @@
          "__new__"      : make_new(class_name),
          }
     pycppclass = metacpp(class_name, _drop_cycles(bases), d)
- 
+
     # cache result early so that the class methods can find the class itself
     setattr(scope, final_class_name, pycppclass)
 
@@ -192,13 +192,10 @@
     for dm_name in cppclass.get_datamember_names():
         cppdm = cppclass.get_datamember(dm_name)
 
-        # here, setattr() can not be used, because a data member can shadow one in
-        # its base class, resulting in the __set__() of its base class being called
-        # by setattr(); so, store directly on the dictionary
-        pycppclass.__dict__[dm_name] = cppdm
+        setattr(pycppclass, dm_name, cppdm)
         import cppyy
         if cppyy._is_static(cppdm):     # TODO: make this a method of cppdm
-            metacpp.__dict__[dm_name] = cppdm
+            setattr(metacpp, dm_name, cppdm)
 
     # the call to register will add back-end specific pythonizations and thus
     # needs to run first, so that the generic pythonizations can use them
@@ -413,7 +410,7 @@
         lib = cppyy._load_dictionary(name)
         _loaded_dictionaries[name] = lib
         return lib
-    
+
 def _init_pythonify():
     # cppyy should not be loaded at the module level, as that will trigger a
     # call to space.getbuiltinmodule(), which will cause cppyy to be loaded
diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py
--- a/pypy/module/cpyext/test/test_typeobject.py
+++ b/pypy/module/cpyext/test/test_typeobject.py
@@ -282,11 +282,41 @@
                      args->ob_type->tp_dict, "copy");
                  Py_INCREF(method);
                  return method;
+             '''),
+            ("get_type_dict", "METH_O",
              '''
-             )
+                PyObject* value = args->ob_type->tp_dict;
+                if (value == NULL) value = Py_None;
+                Py_INCREF(value);
+                return value;
+             '''),
             ])
         obj = foo.new()
         assert module.read_tp_dict(obj) == foo.fooType.copy
+        d = module.get_type_dict(obj)
+        assert type(d) is dict
+        d["_some_attribute"] = 1
+        assert type(obj)._some_attribute == 1
+        del d["_some_attribute"]
+
+        class A(object):
+            pass
+        obj = A()
+        d = module.get_type_dict(obj)
+        assert type(d) is dict
+        d["_some_attribute"] = 1
+        assert type(obj)._some_attribute == 1
+        del d["_some_attribute"]
+
+        d = module.get_type_dict(1)
+        assert type(d) is dict
+        try:
+            d["_some_attribute"] = 1
+        except TypeError:  # on PyPy, int.__dict__ is really immutable
+            pass
+        else:
+            assert int._some_attribute == 1
+            del d["_some_attribute"]
 
     def test_custom_allocation(self):
         foo = self.import_module("foo")
@@ -355,6 +385,21 @@
 
         api.Py_DecRef(ref)
 
+    def test_type_dict(self, space, api):
+        w_class = space.appexec([], """():
+            class A(object):
+                pass
+            return A
+            """)
+        ref = make_ref(space, w_class)
+
+        py_type = rffi.cast(PyTypeObjectPtr, ref)
+        w_dict = from_ref(space, py_type.c_tp_dict)
+        w_name = space.wrap('a')
+        space.setitem(w_dict, w_name, space.wrap(1))
+        assert space.int_w(space.getattr(w_class, w_name)) == 1
+        space.delitem(w_dict, w_name)
+
     def test_multiple_inheritance(self, space, api):
         w_class = space.appexec([], """():
             class A(object):
diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py
--- a/pypy/module/cpyext/typeobject.py
+++ b/pypy/module/cpyext/typeobject.py
@@ -272,12 +272,12 @@
         if len(slot_names) == 1:
             if not getattr(pto, slot_names[0]):
                 setattr(pto, slot_names[0], slot_func_helper)
-        elif (w_type.getname(space) in ('list', 'tuple') and 
+        elif (w_type.getname(space) in ('list', 'tuple') and
               slot_names[0] == 'c_tp_as_number'):
             # XXX hack - hwo can we generalize this? The problem is method
             # names like __mul__ map to more than one slot, and we have no
             # convenient way to indicate which slots CPython have filled
-            # 
+            #
             # We need at least this special case since Numpy checks that
             # (list, tuple) do __not__ fill tp_as_number
             pass
@@ -860,8 +860,8 @@
 
     if w_obj.is_cpytype():
         Py_DecRef(space, pto.c_tp_dict)
-        w_dict = w_obj.getdict(space)
-        pto.c_tp_dict = make_ref(space, w_dict)
+    w_dict = w_obj.getdict(space)
+    pto.c_tp_dict = make_ref(space, w_dict)
 
 @cpython_api([PyTypeObjectPtr, PyTypeObjectPtr], rffi.INT_real, error=CANNOT_FAIL)
 def PyType_IsSubtype(space, a, b):
diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/classdict.py
copy from pypy/objspace/std/dictproxyobject.py
copy to pypy/objspace/std/classdict.py
--- a/pypy/objspace/std/dictproxyobject.py
+++ b/pypy/objspace/std/classdict.py
@@ -7,7 +7,7 @@
 from pypy.objspace.std.typeobject import unwrap_cell
 
 
-class DictProxyStrategy(DictStrategy):
+class ClassDictStrategy(DictStrategy):
     erase, unerase = rerased.new_erasing_pair("dictproxy")
     erase = staticmethod(erase)
     unerase = staticmethod(unerase)
@@ -85,7 +85,8 @@
         return space.newlist_bytes(self.unerase(w_dict.dstorage).dict_w.keys())
 
     def values(self, w_dict):
-        return [unwrap_cell(self.space, w_value) for w_value in self.unerase(w_dict.dstorage).dict_w.itervalues()]
+        return [unwrap_cell(self.space, w_value) for w_value in
+                self.unerase(w_dict.dstorage).dict_w.itervalues()]
 
     def items(self, w_dict):
         space = self.space
@@ -103,13 +104,17 @@
 
     def getiterkeys(self, w_dict):
         return self.unerase(w_dict.dstorage).dict_w.iterkeys()
+
     def getitervalues(self, w_dict):
         return self.unerase(w_dict.dstorage).dict_w.itervalues()
+
     def getiteritems_with_hash(self, w_dict):
         return iteritems_with_hash(self.unerase(w_dict.dstorage).dict_w)
+
     def wrapkey(space, key):
         return space.wrap(key)
+
     def wrapvalue(space, value):
         return unwrap_cell(space, value)
 
-create_iterator_classes(DictProxyStrategy)
+create_iterator_classes(ClassDictStrategy)
diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py
--- a/pypy/objspace/std/dictproxyobject.py
+++ b/pypy/objspace/std/dictproxyobject.py
@@ -1,115 +1,101 @@
-from rpython.rlib import rerased
-from rpython.rlib.objectmodel import iteritems_with_hash
+"""
+Read-only proxy for mappings.
 
-from pypy.interpreter.error import OperationError, oefmt
-from pypy.objspace.std.dictmultiobject import (
-    DictStrategy, create_iterator_classes)
-from pypy.objspace.std.typeobject import unwrap_cell
+Its main use is as the return type of cls.__dict__.
+"""
 
+from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter.error import oefmt
+from pypy.interpreter.gateway import unwrap_spec, WrappedDefault
+from pypy.interpreter.typedef import TypeDef, interp2app
 
-class DictProxyStrategy(DictStrategy):
-    erase, unerase = rerased.new_erasing_pair("dictproxy")
-    erase = staticmethod(erase)
-    unerase = staticmethod(unerase)
+class W_DictProxyObject(W_Root):
+    "Read-only proxy for mappings."
 
-    def __init__(self, space):
-        DictStrategy.__init__(self, space)
+    def __init__(self, w_mapping):
+        self.w_mapping = w_mapping
 
-    def getitem(self, w_dict, w_key):
-        space = self.space
-        w_lookup_type = space.type(w_key)
-        if (space.is_w(w_lookup_type, space.w_str) or  # Most common path first
-            space.abstract_issubclass_w(w_lookup_type, space.w_str)):
-            return self.getitem_str(w_dict, space.str_w(w_key))
-        elif space.abstract_issubclass_w(w_lookup_type, space.w_unicode):
-            try:
-                w_key = space.str(w_key)
-            except OperationError as e:
-                if not e.match(space, space.w_UnicodeEncodeError):
-                    raise
-                # non-ascii unicode is never equal to a byte string
-                return None
-            return self.getitem_str(w_dict, space.str_w(w_key))
-        else:
-            return None
+    @staticmethod
+    def descr_new(space, w_type, w_mapping):
+        raise oefmt(space.w_TypeError, "Cannot create 'dictproxy' instances")
 
-    def getitem_str(self, w_dict, key):
-        return self.unerase(w_dict.dstorage).getdictvalue(self.space, key)
+    def descr_init(self, space, __args__):
+        pass
 
-    def setitem(self, w_dict, w_key, w_value):
-        space = self.space
-        if space.is_w(space.type(w_key), space.w_str):
-            self.setitem_str(w_dict, self.space.str_w(w_key), w_value)
-        else:
-            raise oefmt(space.w_TypeError,
-                        "cannot add non-string keys to dict of a type")
+    def descr_len(self, space):
+        return space.len(self.w_mapping)
 
-    def setitem_str(self, w_dict, key, w_value):
-        w_type = self.unerase(w_dict.dstorage)
-        try:
-            w_type.setdictvalue(self.space, key, w_value)
-        except OperationError as e:
-            if not e.match(self.space, self.space.w_TypeError):
-                raise
-            if not w_type.is_cpytype():
-                raise
-            # Allow cpyext to write to type->tp_dict even in the case
-            # of a builtin type.
-            # Like CPython, we assume that this is only done early
-            # after the type is created, and we don't invalidate any
-            # cache.  User code shoud call PyType_Modified().
-            w_type.dict_w[key] = w_value
+    def descr_getitem(self, space, w_key):
+        return space.getitem(self.w_mapping, w_key)
 
-    def setdefault(self, w_dict, w_key, w_default):
-        w_result = self.getitem(w_dict, w_key)
-        if w_result is not None:
-            return w_result
-        self.setitem(w_dict, w_key, w_default)
-        return w_default
+    def descr_contains(self, space, w_key):
+        return space.contains(self.w_mapping, w_key)
 
-    def delitem(self, w_dict, w_key):
-        space = self.space
-        w_key_type = space.type(w_key)
-        if space.is_w(w_key_type, space.w_str):
-            key = self.space.str_w(w_key)
-            if not self.unerase(w_dict.dstorage).deldictvalue(space, key):
-                raise KeyError
-        else:
-            raise KeyError
+    def descr_iter(self, space):
+        return space.iter(self.w_mapping)
 
-    def length(self, w_dict):
-        return len(self.unerase(w_dict.dstorage).dict_w)
+    def descr_str(self, space):
+        return space.str(self.w_mapping)
 
-    def w_keys(self, w_dict):
-        space = self.space
-        return space.newlist_bytes(self.unerase(w_dict.dstorage).dict_w.keys())
+    def descr_repr(self, space):
+        return space.wrap("dict_proxy(%s)" %
+                                (space.str_w(space.repr(self.w_mapping)),))
 
-    def values(self, w_dict):
-        return [unwrap_cell(self.space, w_value) for w_value in self.unerase(w_dict.dstorage).dict_w.itervalues()]
+    @unwrap_spec(w_default=WrappedDefault(None))
+    def get_w(self, space, w_key, w_default):
+        return space.call_method(self.w_mapping, "get", w_key, w_default)
 
-    def items(self, w_dict):
-        space = self.space
-        return [space.newtuple([space.wrap(key), unwrap_cell(self.space, w_value)])
-                    for (key, w_value) in self.unerase(w_dict.dstorage).dict_w.iteritems()]
+    def keys_w(self, space):
+        return space.call_method(self.w_mapping, "keys")
 
-    def clear(self, w_dict):
-        space = self.space
-        w_type = self.unerase(w_dict.dstorage)
-        if not w_type.is_heaptype():
-            raise oefmt(space.w_TypeError,
-                        "can't clear dictionary of type '%N'", w_type)
-        w_type.dict_w.clear()
-        w_type.mutated(None)
+    def descr_iterkeys(self, space):
+        return space.call_method(self.w_mapping, "iterkeys")
 
-    def getiterkeys(self, w_dict):
-        return self.unerase(w_dict.dstorage).dict_w.iterkeys()
-    def getitervalues(self, w_dict):
-        return self.unerase(w_dict.dstorage).dict_w.itervalues()
-    def getiteritems_with_hash(self, w_dict):
-        return iteritems_with_hash(self.unerase(w_dict.dstorage).dict_w)
-    def wrapkey(space, key):
-        return space.wrap(key)
-    def wrapvalue(space, value):
-        return unwrap_cell(space, value)
+    def values_w(self, space):
+        return space.call_method(self.w_mapping, "values")
 
-create_iterator_classes(DictProxyStrategy)
+    def descr_itervalues(self, space):
+        return space.call_method(self.w_mapping, "itervalues")
+
+    def items_w(self, space):
+        return space.call_method(self.w_mapping, "items")
+
+    def descr_iteritems(self, space):
+        return space.call_method(self.w_mapping, "iteritems")
+
+    def copy_w(self, space):
+        return space.call_method(self.w_mapping, "copy")
+
+cmp_methods = {}
+def make_cmp_method(op):
+    def descr_op(self, space, w_other):
+        return getattr(space, op)(self.w_mapping, w_other)
+    descr_name = 'descr_' + op
+    descr_op.__name__ = descr_name
+    setattr(W_DictProxyObject, descr_name, descr_op)
+    cmp_methods['__%s__' % op] = interp2app(getattr(W_DictProxyObject, descr_name))
+
+for op in ['eq', 'ne', 'gt', 'ge', 'lt', 'le']:
+    make_cmp_method(op)
+
+
+W_DictProxyObject.typedef = TypeDef(
+    'dictproxy',
+    __new__=interp2app(W_DictProxyObject.descr_new),
+    __init__=interp2app(W_DictProxyObject.descr_init),
+    __len__=interp2app(W_DictProxyObject.descr_len),
+    __getitem__=interp2app(W_DictProxyObject.descr_getitem),
+    __contains__=interp2app(W_DictProxyObject.descr_contains),
+    __iter__=interp2app(W_DictProxyObject.descr_iter),
+    __str__=interp2app(W_DictProxyObject.descr_str),
+    __repr__=interp2app(W_DictProxyObject.descr_repr),
+    get=interp2app(W_DictProxyObject.get_w),
+    keys=interp2app(W_DictProxyObject.keys_w),
+    iterkeys=interp2app(W_DictProxyObject.descr_iterkeys),
+    values=interp2app(W_DictProxyObject.values_w),
+    itervalues=interp2app(W_DictProxyObject.descr_itervalues),
+    items=interp2app(W_DictProxyObject.items_w),
+    iteritems=interp2app(W_DictProxyObject.descr_iteritems),
+    copy=interp2app(W_DictProxyObject.copy_w),
+    **cmp_methods
+)
diff --git a/pypy/objspace/std/test/test_dictproxy.py b/pypy/objspace/std/test/test_dictproxy.py
--- a/pypy/objspace/std/test/test_dictproxy.py
+++ b/pypy/objspace/std/test/test_dictproxy.py
@@ -9,37 +9,19 @@
         assert 'a' in NotEmpty.__dict__
         assert 'a' in NotEmpty.__dict__.keys()
         assert 'b' not in NotEmpty.__dict__
-        NotEmpty.__dict__['b'] = 4
-        assert NotEmpty.b == 4
-        del NotEmpty.__dict__['b']
         assert NotEmpty.__dict__.get("b") is None
+        raises(TypeError, "NotEmpty.__dict__['b'] = 4")
         raises(TypeError, 'NotEmpty.__dict__[15] = "y"')
-        raises(KeyError, 'del NotEmpty.__dict__[15]')
+        raises(TypeError, 'del NotEmpty.__dict__[15]')
 
-        assert NotEmpty.__dict__.setdefault("string", 1) == 1
-        assert NotEmpty.__dict__.setdefault("string", 2) == 1
-        assert NotEmpty.string == 1
-        raises(TypeError, 'NotEmpty.__dict__.setdefault(15, 1)')
-
-    def test_dictproxy_popitem(self):
-        class A(object):
-            a = 42
-        seen = 0
-        try:
-            while True:
-                key, value = A.__dict__.popitem()
-                if key == 'a':
-                    assert value == 42
-                    seen += 1
-        except KeyError:
-            pass
-        assert seen == 1
+        raises(AttributeError, 'NotEmpty.__dict__.setdefault')
 
     def test_dictproxy_getitem(self):
         class NotEmpty(object):
             a = 1
         assert 'a' in NotEmpty.__dict__
-        class substr(str): pass
+        class substr(str):
+            pass
         assert substr('a') in NotEmpty.__dict__
         assert u'a' in NotEmpty.__dict__
         assert NotEmpty.__dict__[u'a'] == 1
@@ -62,15 +44,40 @@
         class a(object):
             pass
         s1 = repr(a.__dict__)
+        assert s1.startswith('dict_proxy({') and s1.endswith('})')
         s2 = str(a.__dict__)
-        assert s1 == s2
-        assert s1.startswith('{') and s1.endswith('}')
+        assert s1 == 'dict_proxy(%s)' % s2
 
     def test_immutable_dict_on_builtin_type(self):
         raises(TypeError, "int.__dict__['a'] = 1")
-        raises(TypeError, int.__dict__.popitem)
-        raises(TypeError, int.__dict__.clear)
+        raises((AttributeError, TypeError), "int.__dict__.popitem()")
+        raises((AttributeError, TypeError), "int.__dict__.clear()")
+
+    def test_dictproxy(self):
+        dictproxy = type(int.__dict__)
+        assert dictproxy is not dict
+        assert dictproxy.__name__ == 'dictproxy'
+        raises(TypeError, dictproxy)
+
+        mapping = {'a': 1}
+        raises(TypeError, dictproxy, mapping)
+
+        class A(object):
+            a = 1
+
+        proxy = A.__dict__
+        mapping = dict(proxy)
+        assert proxy['a'] == 1
+        assert 'a' in proxy
+        assert 'z' not in proxy
+        assert repr(proxy) == 'dict_proxy(%r)' % mapping
+        assert proxy.keys() == mapping.keys()
+        assert list(proxy.iterkeys()) == proxy.keys()
+        assert list(proxy.itervalues()) == proxy.values()
+        assert list(proxy.iteritems()) == proxy.items()
+        raises(TypeError, "proxy['a'] = 4")
+        raises(TypeError, "del proxy['a']")
+        raises(AttributeError, "proxy.clear()")
 
 class AppTestUserObjectMethodCache(AppTestUserObject):
     spaceconfig = {"objspace.std.withmethodcachecounter": True}
-
diff --git a/pypy/objspace/std/test/test_typeobject.py b/pypy/objspace/std/test/test_typeobject.py
--- a/pypy/objspace/std/test/test_typeobject.py
+++ b/pypy/objspace/std/test/test_typeobject.py
@@ -968,7 +968,6 @@
         raises(TypeError, setattr, list, 'foobar', 42)
         raises(TypeError, delattr, dict, 'keys')
         raises(TypeError, 'int.__dict__["a"] = 1')
-        raises(TypeError, 'int.__dict__.clear()')
 
     def test_nontype_in_mro(self):
         class OldStyle:
@@ -1026,10 +1025,9 @@
             pass
 
         a = A()
+        d = A.__dict__
         A.x = 1
-        assert A.__dict__["x"] == 1
-        A.__dict__['x'] = 5
-        assert A.x == 5
+        assert d["x"] == 1
 
     def test_we_already_got_one_1(self):
         # Issue #2079: highly obscure: CPython complains if we say
diff --git a/pypy/objspace/std/typeobject.py b/pypy/objspace/std/typeobject.py
--- a/pypy/objspace/std/typeobject.py
+++ b/pypy/objspace/std/typeobject.py
@@ -3,8 +3,8 @@
 from pypy.interpreter.baseobjspace import W_Root, SpaceCache
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.interpreter.function import Function, StaticMethod
-from pypy.interpreter.typedef import weakref_descr, GetSetProperty,\
-     descr_get_dict, dict_descr, Member, TypeDef
+from pypy.interpreter.typedef import (
+    weakref_descr, GetSetProperty, dict_descr, Member, TypeDef)
 from pypy.interpreter.astcompiler.misc import mangle
 from pypy.module.__builtin__ import abstractinst
 
@@ -346,7 +346,7 @@
     def deldictvalue(self, space, key):
         if self.lazyloaders:
             self._cleanup_()    # force un-lazification
-        if not self.is_heaptype():
+        if not (self.is_heaptype() or self.is_cpytype()):
             raise oefmt(space.w_TypeError,
                         "can't delete attributes on type object '%N'", self)
         try:
@@ -485,12 +485,12 @@
                 self.getdictvalue(self.space, attr)
             del self.lazyloaders
 
-    def getdict(self, space): # returning a dict-proxy!
-        from pypy.objspace.std.dictproxyobject import DictProxyStrategy
+    def getdict(self, space):
+        from pypy.objspace.std.classdict import ClassDictStrategy
         from pypy.objspace.std.dictmultiobject import W_DictObject
         if self.lazyloaders:
             self._cleanup_()    # force un-lazification
-        strategy = space.fromcache(DictProxyStrategy)
+        strategy = space.fromcache(ClassDictStrategy)
         storage = strategy.erase(self)
         return W_DictObject(space, strategy, storage)
 
@@ -910,13 +910,21 @@
     return space.newbool(
         abstractinst.p_recursive_isinstance_type_w(space, w_inst, w_obj))
 
+def type_get_dict(space, w_cls):
+    w_cls = _check(space, w_cls)
+    from pypy.objspace.std.dictproxyobject import W_DictProxyObject
+    w_dict = w_cls.getdict(space)
+    if w_dict is None:
+        return space.w_None
+    return W_DictProxyObject(w_dict)
+
 W_TypeObject.typedef = TypeDef("type",
     __new__ = gateway.interp2app(descr__new__),
     __name__ = GetSetProperty(descr_get__name__, descr_set__name__),
     __bases__ = GetSetProperty(descr_get__bases__, descr_set__bases__),
     __base__ = GetSetProperty(descr__base),
     __mro__ = GetSetProperty(descr_get__mro__),
-    __dict__ = GetSetProperty(descr_get_dict),
+    __dict__=GetSetProperty(type_get_dict),
     __doc__ = GetSetProperty(descr__doc),
     mro = gateway.interp2app(descr_mro),
     __flags__ = GetSetProperty(descr__flags),


More information about the pypy-commit mailing list