8000 [3.6] bpo-34320: Fix dict(o) didn't copy order of dict subclass (GH-8… · python/cpython@d45a961 · GitHub
[go: up one dir, main page]

Skip to content

Commit d45a961

Browse files
[3.6] bpo-34320: Fix dict(o) didn't copy order of dict subclass (GH-8624) (GH-9583)
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> https:// 8000 bugs.python.org/issue34320
1 parent dc335ae commit d45a961

File tree

5 files changed

+62
-1
lines changed

5 files changed

+62
-1
lines changed

Lib/test/test_builtin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,15 @@ class B:
18411841
with self.assertRaises(TypeError):
18421842
type('A', (B,), {'__slots__': '__weakref__'})
18431843

1844+
def test_namespace_order(self):
1845+
# bpo-34320: namespace should preserve order
1846+
od = collections.OrderedDict([('a', 1), ('b', 2)])
1847+
od.move_to_end('a')
1848+
expected = list(od.items())
1849+
1850+
C = type('C', (), od)
1851+
self.assertEqual(list(C.__dict__.items())[:2], [('b', 2), ('a', 1)])
1852+
18441853

18451854
def load_tests(loader, tests, pattern):
18461855
from doctest import DocTestSuite

Lib/test/test_call.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import collections
12
import datetime
23
import unittest
34
from test.support import cpython_only
@@ -6,6 +7,23 @@
67
except ImportError:
78
_testcapi = None
89

10+
11+
class FunctionCalls(unittest.TestCase):
12+
13+
def test_kwargs_order(self):
14+
# bpo-34320: **kwargs should preserve order of passed OrderedDict
15+
od = collections.OrderedDict([('a', 1), ('b', 2)])
16+
od.move_to_end('a')
17+
expected = list(od.items())
18+
19+
def fn(**kw):
20+
return kw
21+
22+
res = fn(**od)
23+
self.assertIsInstance(res, dict)
24+
self.assertEqual(list(res.items()), expected)
25+
26+
927
# The test cases here cover several paths through the function calling
1028
# code. They depend on the METH_XXX flag that is used to define a C
1129
# function, which can't be verified from Python. If the METH_XXX decl

Lib/test/test_dict.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,37 @@ def iter_and_mutate():
11761176

11771177
self.assertRaises(RuntimeError, iter_and_mutate)
11781178

1179+
@support.cpython_only
1180+
def test_dict_copy_order(self):
1181+
# bpo-34320
1182+
od = collections.OrderedDict([('a', 1), ('b', 2)])
1183+
od.move_to_end('a')
1184+
expected = list(od.items())
1185+
1186+
copy = dict(od)
1187+
self.assertEqual(list(copy.items()), expected)
1188+
1189+
# dict subclass doesn't override __iter__
1190+
class CustomDict(dict):
1191+
pass
1192+
1193+
pairs = [('a', 1), ('b', 2), ('c', 3)]
1194+
1195+
d = CustomDict(pairs)
1196+
self.assertEqual(pairs, list(dict(d).items()))
1197+
1198+
class CustomReversedDict(dict):
1199+
def keys(self):
1200+
return reversed(list(dict.keys(self)))
1201+
1202+
__iter__ = keys
1203+
1204+
def items(self):
1205+
return reversed(dict.items(self))
1206+
1207+
d = CustomReversedDict(pairs)
1208+
self.assertEqual(pairs[::-1], list(dict(d).items()))
1209+
11791210

11801211
class CAPITest(unittest.TestCase):
11811212

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``dict(od)`` didn't copy iteration order of OrderedDict.

Objects/dictobject.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ static Py_ssize_t lookdict_split(PyDictObject *mp, PyObject *key,
237237

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

240+
static PyObject* dict_iter(PyDictObject *dict);
241+
240242
/*Global counter used to set ma_version_tag field of dictionary.
241243
* It is incremented each time that a dictionary is created and each
242244
* time that a dictionary is modified. */
@@ -2482,7 +2484,7 @@ dict_merge(PyObject *a, PyObject *b, int override)
24822484
return -1;
24832485
}
24842486
mp = (PyDictObject*)a;
2485-
if (PyDict_Check(b)) {
2487+
if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == (getiterfunc)dict_iter)) {
24862488
other = (PyDictObject*)b;
24872489
if (other == mp || other->ma_used == 0)
24882490
/* a.update(a) or a.update({}); nothing to do */

0 commit comments

Comments
 (0)
0