[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