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