Author: Armin Rigo <ar...@tunes.org>
Branch: py3.5
Changeset: r90080:82e97c392624
Date: 2017-02-13 14:33 +0100
http://bitbucket.org/pypy/pypy/changeset/82e97c392624/

Log:    hg merge default

diff --git a/lib-python/2.7/collections.py b/lib-python/2.7/collections.py
--- a/lib-python/2.7/collections.py
+++ b/lib-python/2.7/collections.py
@@ -33,6 +33,10 @@
     from __pypy__ import reversed_dict as _reversed_dict
 except ImportError:
     _reversed_dict = None     # don't have ordered dicts
+try:
+    from __pypy__ import dict_popitem_first as _dict_popitem_first
+except ImportError:
+    _dict_popitem_first = None
 
 try:
     from thread import get_ident as _get_ident
@@ -44,6 +48,17 @@
 ### OrderedDict
 
################################################################################
 
+if _dict_popitem_first is None:
+    def _dict_popitem_first(self):
+        it = dict.iteritems(self)
+        try:
+            k, v = it.next()
+        except StopIteration:
+            raise KeyError('dictionary is empty')
+        dict.__delitem__(self, k)
+        return (k, v)
+
+
 class OrderedDict(dict):
     '''Dictionary that remembers insertion order.
 
@@ -68,12 +83,7 @@
         if last:
             return dict.popitem(self)
         else:
-            it = dict.__iter__(self)
-            try:
-                k = it.next()
-            except StopIteration:
-                raise KeyError('dictionary is empty')
-            return (k, self.pop(k))
+            return _dict_popitem_first(self)
 
     def __repr__(self, _repr_running={}):
         'od.__repr__() <==> repr(od)'
diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py
--- a/pypy/module/__pypy__/__init__.py
+++ b/pypy/module/__pypy__/__init__.py
@@ -78,6 +78,7 @@
         'add_memory_pressure'       : 'interp_magic.add_memory_pressure',
         'newdict'                   : 'interp_dict.newdict',
         'reversed_dict'             : 'interp_dict.reversed_dict',
+        'dict_popitem_first'        : 'interp_dict.dict_popitem_first',
         'delitem_if_value_is'       : 'interp_dict.delitem_if_value_is',
         'move_to_end'               : 'interp_dict.move_to_end',
         'strategy'                  : 'interp_magic.strategy',  # dict,set,list
diff --git a/pypy/module/__pypy__/interp_dict.py 
b/pypy/module/__pypy__/interp_dict.py
--- a/pypy/module/__pypy__/interp_dict.py
+++ b/pypy/module/__pypy__/interp_dict.py
@@ -45,6 +45,27 @@
         raise OperationError(space.w_TypeError, space.w_None)
     return w_obj.nondescr_reversed_dict(space)
 
+def dict_popitem_first(space, w_obj):
+    """Interp-level implementation of OrderedDict.popitem(last=False).
+    """
+    from pypy.objspace.std.dictmultiobject import W_DictMultiObject
+    if not isinstance(w_obj, W_DictMultiObject):
+        raise OperationError(space.w_TypeError, space.w_None)
+    return w_obj.nondescr_popitem_first(space)
+
+def delitem_if_value_is(space, w_obj, w_key, w_value):
+    """Atomic equivalent to: 'if dict.get(key) is value: del dict[key]'.
+
+    SPECIAL USE CASES ONLY!  Avoid using on dicts which are specialized,
+    e.g. to int or str keys, because it switches to the object strategy.
+    Also, the 'is' operation is really pointer equality, so avoid using
+    it if 'value' is an immutable object like int or str.
+    """
+    from pypy.objspace.std.dictmultiobject import W_DictMultiObject
+    if not isinstance(w_obj, W_DictMultiObject):
+        raise OperationError(space.w_TypeError, space.w_None)
+    return w_obj.nondescr_delitem_if_value_is(space, w_key, w_value)
+
 @unwrap_spec(last=bool)
 def move_to_end(space, w_obj, w_key, last=True):
     """Move the key in a dictionary object into the first or last position.
@@ -59,16 +80,3 @@
     if not isinstance(w_obj, W_DictMultiObject):
         raise OperationError(space.w_TypeError, space.w_None)
     return w_obj.nondescr_move_to_end(space, w_key, last)
-
-def delitem_if_value_is(space, w_obj, w_key, w_value):
-    """Atomic equivalent to: 'if dict.get(key) is value: del dict[key]'.
-
-    SPECIAL USE CASES ONLY!  Avoid using on dicts which are specialized,
-    e.g. to int or str keys, because it switches to the object strategy.
-    Also, the 'is' operation is really pointer equality, so avoid using
-    it if 'value' is an immutable object like int or str.
-    """
-    from pypy.objspace.std.dictmultiobject import W_DictMultiObject
-    if not isinstance(w_obj, W_DictMultiObject):
-        raise OperationError(space.w_TypeError, space.w_None)
-    return w_obj.nondescr_delitem_if_value_is(space, w_key, w_value)
diff --git a/pypy/objspace/std/dictmultiobject.py 
b/pypy/objspace/std/dictmultiobject.py
--- a/pypy/objspace/std/dictmultiobject.py
+++ b/pypy/objspace/std/dictmultiobject.py
@@ -102,13 +102,6 @@
             result[key] = val
         return result
 
-    def missing_method(w_dict, space, w_key):
-        if not space.is_w(space.type(w_dict), space.w_dict):
-            w_missing = space.lookup(w_dict, '__missing__')
-            if w_missing is not None:
-                return space.get_and_call_function(w_missing, w_dict, w_key)
-        return None
-
     def initialize_content(self, list_pairs_w):
         for w_k, w_v in list_pairs_w:
             self.setitem(w_k, w_v)
@@ -191,9 +184,11 @@
         if w_value is not None:
             return w_value
 
-        w_missing_item = self.missing_method(space, w_key)
-        if w_missing_item is not None:
-            return w_missing_item
+        # if there is a __missing__ method, call it
+        if not space.is_w(space.type(self), space.w_dict):
+            w_missing = space.lookup(self, '__missing__')
+            if w_missing is not None:
+                return space.get_and_call_function(w_missing, self, w_key)
 
         space.raise_key_error(w_key)
 
@@ -206,6 +201,14 @@
         except KeyError:
             space.raise_key_error(w_key)
 
+    def internal_delitem(self, w_key):
+        try:
+            self.delitem(w_key)
+        except KeyError:
+            raise oefmt(self.space.w_RuntimeError,
+                        "an internal 'del' on the dictionary failed to find "
+                        "the key")
+
     def descr_copy(self, space):
         """D.copy() -> a shallow copy of D"""
         return self.copy()
@@ -234,6 +237,14 @@
             w_keys = self.w_keys()
             return space.call_method(w_keys, '__reversed__')
 
+    def nondescr_delitem_if_value_is(self, space, w_key, w_value):
+        """Not exposed directly to app-level, but used by
+        _weakref._remove_dead_weakref and via __pypy__.delitem_if_value_is().
+        """
+        strategy = self.ensure_object_strategy()
+        d = strategy.unerase(self.dstorage)
+        objectmodel.delitem_if_value_is(d, w_key, w_value)
+
     def nondescr_move_to_end(self, space, w_key, last_flag):
         """Not exposed directly to app-level, but via __pypy__.move_to_end().
         """
@@ -246,7 +257,7 @@
             if w_value is None:
                 space.raise_key_error(w_key)
             else:
-                self.delitem(w_key)
+                self.internal_delitem(w_key)
                 if last_flag:
                     self.setitem(w_key, w_value)
                 else:
@@ -265,13 +276,14 @@
                     for i in range(len(keys_w)):
                         self.setitem(keys_w[i], values_w[i])
 
-    def nondescr_delitem_if_value_is(self, space, w_key, w_value):
-        """Not exposed directly to app-level, but used by
-        _weakref._remove_dead_weakref and via __pypy__.delitem_if_value_is().
+    def nondescr_popitem_first(self, space):
+        """Not exposed directly to app-level, but via __pypy__.popitem_first().
         """
-        strategy = self.ensure_object_strategy()
-        d = strategy.unerase(self.dstorage)
-        objectmodel.delitem_if_value_is(d, w_key, w_value)
+        w_key, w_value = self.iteritems().next_item()
+        if w_key is None:
+            raise oefmt(space.w_KeyError, "popitem(): dictionary is empty")
+        self.internal_delitem(w_key)
+        return space.newtuple([w_key, w_value])
 
     def descr_clear(self, space):
         """D.clear() -> None.  Remove all items from D."""
@@ -288,6 +300,13 @@
         corresponding value\nIf key is not found, d is returned if given,
         otherwise KeyError is raised
         """
+        strategy = self.get_strategy()
+        if strategy.has_pop:
+            try:
+                return strategy.pop(self, w_key, w_default)
+            except KeyError:
+                raise space.raise_key_error(w_key)
+        # fall-back
         w_item = self.getitem(w_key)
         if w_item is None:
             if w_default is not None:
@@ -295,7 +314,7 @@
             else:
                 space.raise_key_error(w_key)
         else:
-            self.delitem(w_key)
+            self.internal_delitem(w_key)
             return w_item
 
     def descr_popitem(self, space):
@@ -536,8 +555,8 @@
 
     has_iterreversed = False
     has_move_to_end = False
-    # no 'getiterreversed' and no 'move_to_end': no default
-    # implementation available
+    has_pop = False
+    # ^^^ no default implementation available for these methods
 
     def rev_update1_dict_dict(self, w_dict, w_updatedict):
         iteritems = self.iteritems(w_dict)
@@ -824,6 +843,9 @@
     if hasattr(dictimpl, 'move_to_end'):
         dictimpl.has_move_to_end = True
 
+    if hasattr(dictimpl, 'pop'):
+        dictimpl.has_pop = True
+
     @jit.look_inside_iff(lambda self, w_dict, w_updatedict:
                          w_dict_unrolling_heuristic(w_dict))
     def rev_update1_dict_dict(self, w_dict, w_updatedict):
@@ -977,6 +999,21 @@
         key, value = self.unerase(w_dict.dstorage).popitem()
         return (self.wrap(key), value)
 
+    def pop(self, w_dict, w_key, w_default):
+        space = self.space
+        if self.is_correct_type(w_key):
+            key = self.unwrap(w_key)
+            d = self.unerase(w_dict.dstorage)
+            if w_default is None:
+                return d.pop(key)
+            else:
+                return d.pop(key, w_default)
+        elif self._never_equal_to(space.type(w_key)):
+            raise KeyError
+        else:
+            self.switch_to_object_strategy(w_dict)
+            return w_dict.get_strategy().pop(w_dict, w_key, w_default)
+
     def clear(self, w_dict):
         self.unerase(w_dict.dstorage).clear()
 
diff --git a/pypy/objspace/std/test/test_dictmultiobject.py 
b/pypy/objspace/std/test/test_dictmultiobject.py
--- a/pypy/objspace/std/test/test_dictmultiobject.py
+++ b/pypy/objspace/std/test/test_dictmultiobject.py
@@ -266,10 +266,36 @@
         d = {1: 2, 3: 4, 5: 6}
         it = __pypy__.reversed_dict(d)
         key = next(it)
-        assert key in [1, 3, 5]
+        assert key in [1, 3, 5]   # on CPython, dicts are not ordered
         del d[key]
         raises(RuntimeError, next, it)
 
+    def test_dict_popitem_first(self):
+        import __pypy__
+        d = {"a": 5}
+        assert __pypy__.dict_popitem_first(d) == ("a", 5)
+        raises(KeyError, __pypy__.dict_popitem_first, d)
+
+        def kwdict(**k):
+            return k
+        d = kwdict(a=55)
+        assert __pypy__.dict_popitem_first(d) == ("a", 55)
+        raises(KeyError, __pypy__.dict_popitem_first, d)
+
+    def test_delitem_if_value_is(self):
+        import __pypy__
+        class X:
+            pass
+        x2 = X()
+        x3 = X()
+        d = {2: x2, 3: x3}
+        __pypy__.delitem_if_value_is(d, 2, x3)
+        assert d == {2: x2, 3: x3}
+        __pypy__.delitem_if_value_is(d, 2, x2)
+        assert d == {3: x3}
+        __pypy__.delitem_if_value_is(d, 2, x3)
+        assert d == {3: x3}
+
     def test_move_to_end(self):
         import __pypy__
         raises(KeyError, __pypy__.move_to_end, {}, 'foo')
@@ -294,20 +320,6 @@
                     assert list(d) == [key] + other_keys
                 raises(KeyError, __pypy__.move_to_end, d, key * 3, last=last)
 
-    def test_delitem_if_value_is(self):
-        import __pypy__
-        class X:
-            pass
-        x2 = X()
-        x3 = X()
-        d = {2: x2, 3: x3}
-        __pypy__.delitem_if_value_is(d, 2, x3)
-        assert d == {2: x2, 3: x3}
-        __pypy__.delitem_if_value_is(d, 2, x2)
-        assert d == {3: x3}
-        __pypy__.delitem_if_value_is(d, 2, x3)
-        assert d == {3: x3}
-
     def test_keys(self):
         d = {1: 2, 3: 4}
         kys = list(d.keys())
@@ -618,6 +630,18 @@
             else:
                 assert False, 'Expected KeyError'
 
+    def test_pop_switching_strategy(self):
+        class Foo:
+            def __hash__(self):
+                return hash("a")
+            def __eq__(self, other):
+                return other == "a"
+        d = {"a": 42}
+        x = d.pop(Foo())
+        assert x == 42 and len(d) == 0
+        d = {"b": 43}
+        raises(KeyError, d.pop, Foo())
+
     def test_no_len_on_dict_iter(self):
         iterable = {1: 2, 3: 4}
         raises(TypeError, len, iter(iterable))
@@ -694,6 +718,31 @@
         setattr(a, s, 123)
         assert holder.seen is s
 
+    def test_internal_delitem(self):
+        class K:
+            def __hash__(self):
+                return 42
+            def __eq__(self, other):
+                if is_equal[0]:
+                    is_equal[0] -= 1
+                    return True
+                return False
+        is_equal = [0]
+        k1 = K()
+        k2 = K()
+        d = {k1: 1, k2: 2}
+        k3 = K()
+        is_equal = [1]
+        try:
+            x = d.pop(k3)
+        except RuntimeError:
+            # This used to give a Fatal RPython error: KeyError.
+            # Now at least it should raise an app-level RuntimeError,
+            # or just work.
+            assert len(d) == 2
+        else:
+            assert (x == 1 or x == 2) and len(d) == 1
+
 
 class AppTestDictViews:
     def test_dictview(self):
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to