Skip to content

Commit c8a3541

Browse files
joernheisslermiss-islington
authored andcommitted
bpo-35224: Reverse evaluation order of key: value in dict comprehensions (GH-14139)
… as proposed in PEP 572; key is now evaluated before value. https://bugs.python.org/issue35224
1 parent bb110cc commit c8a3541

File tree

8 files changed

+53
-7
lines changed

8 files changed

+53
-7
lines changed

Doc/library/dis.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,10 +645,12 @@ the original TOS1.
645645

646646
.. opcode:: MAP_ADD (i)
647647

648-
Calls ``dict.setitem(TOS1[-i], TOS, TOS1)``. Used to implement dict
648+
Calls ``dict.__setitem__(TOS1[-i], TOS1, TOS)``. Used to implement dict
649649
comprehensions.
650650

651651
.. versionadded:: 3.1
652+
.. versionchanged:: 3.8
653+
Map value is TOS and map key is TOS1. Before, those were reversed.
652654

653655
For all of the :opcode:`SET_ADD`, :opcode:`LIST_APPEND` and :opcode:`MAP_ADD`
654656
instructions, while the added value or key/value pair is popped off, the

Doc/reference/expressions.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,12 @@ all mutable objects.) Clashes between duplicate keys are not detected; the last
337337
datum (textually rightmost in the display) stored for a given key value
338338
prevails.
339339

340+
.. versionchanged:: 3.8
341+
Prior to Python 3.8, in dict comprehensions, the evaluation order of key
342+
and value was not well-defined. In CPython, the value was evaluated before
343+
the key. Starting with 3.8, the key is evaluated before the value, as
344+
proposed by :pep:`572`.
345+
340346

341347
.. _genexpr:
342348

Lib/test/test_dictcomps.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,35 @@ def test_illegal_assignment(self):
8181
compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "<test>",
8282
"exec")
8383

84+
def test_evaluation_order(self):
85+
expected = {
86+
'H': 'W',
87+
'e': 'o',
88+
'l': 'l',
89+
'o': 'd',
90+
}
91+
92+
expected_calls = [
93+
('key', 'H'), ('value', 'W'),
94+
('key', 'e'), ('value', 'o'),
95+
('key', 'l'), ('value', 'r'),
96+
('key', 'l'), ('value', 'l'),
97+
('key', 'o'), ('value', 'd'),
98+
]
99+
100+
actual_calls = []
101+
102+
def add_call(pos, value):
103+
actual_calls.append((pos, value))
104+
return value
105+
106+
actual = {
107+
add_call('key', k): add_call('value', v)
108+
for k, v in zip('Hello', 'World')
109+
}
110+
111+
self.assertEqual(actual, expected)
112+
self.assertEqual(actual_calls, expected_calls)
84113

85114
if __name__ == "__main__":
86115
unittest.main()

Lib/test/test_named_expressions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ def test_named_expression_assignment_15(self):
212212

213213
self.assertEqual(a, False)
214214

215+
def test_named_expression_assignment_16(self):
216+
a, b = 1, 2
217+
fib = {(c := a): (a := b) + (b := a + c) - b for __ in range(6)}
218+
self.assertEqual(fib, {1: 2, 2: 3, 3: 5, 5: 8, 8: 13, 13: 21})
219+
215220

216221
class NamedExpressionScopeTest(unittest.TestCase):
217222

Lib/test/test_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,8 @@ def test_named_expressions(self):
473473
self.check_suite("foo(b := 2, a=1)")
474474
self.check_suite("foo((b := 2), a=1)")
475475
self.check_suite("foo(c=(b := 2), a=1)")
476+
self.check_suite("{(x := C(i)).q: x for i in y}")
477+
476478

477479
#
478480
# Second, we take *invalid* trees and make sure we get ParserError
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Reverse evaluation order of key: value in dict comprehensions as proposed in PEP 572.
2+
I.e. in ``{k: v for ...}``, ``k`` will be evaluated before ``v``.

Python/ceval.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2940,8 +2940,8 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
29402940
}
29412941

29422942
case TARGET(MAP_ADD): {
2943-
PyObject *key = TOP();
2944-
PyObject *value = SECOND();
2943+
PyObject *value = TOP();
2944+
PyObject *key = SECOND();
29452945
PyObject *map;
29462946
int err;
29472947
STACK_SHRINK(2);

Python/compile.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4238,10 +4238,10 @@ compiler_sync_comprehension_generator(struct compiler *c,
42384238
ADDOP_I(c, SET_ADD, gen_index + 1);
42394239
break;
42404240
case COMP_DICTCOMP:
4241-
/* With 'd[k] = v', v is evaluated before k, so we do
4241+
/* With '{k: v}', k is evaluated before v, so we do
42424242
the same. */
4243-
VISIT(c, expr, val);
42444243
VISIT(c, expr, elt);
4244+
VISIT(c, expr, val);
42454245
ADDOP_I(c, MAP_ADD, gen_index + 1);
42464246
break;
42474247
default:
@@ -4327,10 +4327,10 @@ compiler_async_comprehension_generator(struct compiler *c,
43274327
ADDOP_I(c, SET_ADD, gen_index + 1);
43284328
break;
43294329
case COMP_DICTCOMP:
4330-
/* With 'd[k] = v', v is evaluated before k, so we do
4330+
/* With '{k: v}', k is evaluated before v, so we do
43314331
the same. */
4332-
VISIT(c, expr, val);
43334332
VISIT(c, expr, elt);
4333+
VISIT(c, expr, val);
43344334
ADDOP_I(c, MAP_ADD, gen_index + 1);
43354335
break;
43364336
default:

0 commit comments

Comments
 (0)