Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-34320: Fix dict(o) didn't copy order of dict subclass #8624

Merged
merged 5 commits into from
Sep 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1968,6 +1968,15 @@ class B:
with self.assertRaises(TypeError):
type('A', (B,), {'__slots__': '__weakref__'})

def test_namespace_order(self):
# bpo-34320: namespace should preserve order
od = collections.OrderedDict([('a', 1), ('b', 2)])
od.move_to_end('a')
expected = list(od.items())

C = type('C', (), od)
self.assertEqual(list(C.__dict__.items())[:2], [('b', 2), ('a', 1)])


def load_tests(loader, tests, pattern):
from doctest import DocTestSuite
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@
import collections
import itertools


class FunctionCalls(unittest.TestCase):

def test_kwargs_order(self):
# bpo-34320: **kwargs should preserve order of passed OrderedDict
od = collections.OrderedDict([('a', 1), ('b', 2)])
od.move_to_end('a')
expected = list(od.items())

def fn(**kw):
return kw

res = fn(**od)
self.assertIsInstance(res, dict)
self.assertEqual(list(res.items()), expected)


# The test cases here cover several paths through the function calling
# code. They depend on the METH_XXX flag that is used to define a C
# function, which can't be verified from Python. If the METH_XXX decl
Expand Down
30 changes: 30 additions & 0 deletions Lib/test/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,36 @@ def iter_and_mutate():

self.assertRaises(RuntimeError, iter_and_mutate)

def test_dict_copy_order(self):
# bpo-34320
od = collections.OrderedDict([('a', 1), ('b', 2)])
od.move_to_end('a')
expected = list(od.items())

copy = dict(od)
self.assertEqual(list(copy.items()), expected)

# dict subclass doesn't override __iter__
class CustomDict(dict):
pass

pairs = [('a', 1), ('b', 2), ('c', 3)]

d = CustomDict(pairs)
self.assertEqual(pairs, list(dict(d).items()))

class CustomReversedDict(dict):
def keys(self):
return reversed(list(dict.keys(self)))

__iter__ = keys

def items(self):
return reversed(dict.items(self))

d = CustomReversedDict(pairs)
self.assertEqual(pairs[::-1], list(dict(d).items()))


class CAPITest(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``dict(od)`` didn't copy iteration order of OrderedDict.
4 changes: 3 additions & 1 deletion Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ static Py_ssize_t lookdict_split(PyDictObject *mp, PyObject *key,

static int dictresize(PyDictObject *mp, Py_ssize_t minused);

static PyObject* dict_iter(PyDictObject *dict);

/*Global counter used to set ma_version_tag field of dictionary.
* It is incremented each time that a dictionary is created and each
* time that a dictionary is modified. */
Expand Down Expand Up @@ -2379,7 +2381,7 @@ dict_merge(PyObject *a, PyObject *b, int override)
return -1;
}
mp = (PyDictObject*)a;
if (PyDict_Check(b)) {
if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == (getiterfunc)dict_iter)) {
other = (PyDictObject*)b;
if (other == mp || other->ma_used == 0)
/* a.update(a) or a.update({}); nothing to do */
Expand Down