8000 [3.6] bpo-34320: Fix dict(o) didn't copy order of dict subclass (GH-8624) by miss-islington · Pull Request #9583 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

[3.6] bpo-34320: Fix dict(o) didn't copy order of dict subclass (GH-8624) #9583

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

Merged
merged 3 commits into from
Sep 26, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

8000
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
bpo-34320: Fix dict(o) didn't copy order of dict subclass (GH-8624)
When dict subclass overrides order (`__iter__()`, `keys()`, and `items()`), `dict(o)`
should use it instead of dict ordering.

https://bugs.python.org/issue34320
(cherry picked from commit 2aaf98c)

Co-authored-by: INADA Naoki <methane@users.noreply.github.com>
  • Loading branch information
methane authored and miss-islington committed Sep 26, 2018
commit 11f26b1389512fd66c2e55ab6cf7425ab740bc0c
9 changes: 9 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1841,6 +1841,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
8000
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
except ImportError:
_testcapi = None


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 @@ -1176,6 +1176,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 @@ -237,6 +237,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 @@ -2482,7 +2484,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
0