Skip to content
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
4 changes: 3 additions & 1 deletion Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -645,10 +645,12 @@ the original TOS1.

.. opcode:: MAP_ADD (i)

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

.. versionadded:: 3.1
.. versionchanged:: 3.8
Map value is TOS and map key is TOS1. Before, those were reversed.

For all of the :opcode:`SET_ADD`, :opcode:`LIST_APPEND` and :opcode:`MAP_ADD`
instructions, while the added value or key/value pair is popped off, the
Expand Down
6 changes: 6 additions & 0 deletions Doc/reference/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,12 @@ all mutable objects.) Clashes between duplicate keys are not detected; the last
datum (textually rightmost in the display) stored for a given key value
prevails.

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


.. _genexpr:

Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_dictcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,35 @@ def test_illegal_assignment(self):
compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "<test>",
"exec")

def test_evaluation_order(self):
expected = {
'H': 'W',
'e': 'o',
'l': 'l',
'o': 'd',
}

expected_calls = [
('key', 'H'), ('value', 'W'),
('key', 'e'), ('value', 'o'),
('key', 'l'), ('value', 'r'),
('key', 'l'), ('value', 'l'),
('key', 'o'), ('value', 'd'),
]

actual_calls = []

def add_call(pos, value):
actual_calls.append((pos, value))
return value

actual = {
add_call('key', k): add_call('value', v)
for k, v in zip('Hello', 'World')
}

self.assertEqual(actual, expected)
self.assertEqual(actual_calls, expected_calls)

if __name__ == "__main__":
unittest.main()
5 changes: 5 additions & 0 deletions Lib/test/test_named_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ def test_named_expression_assignment_15(self):

self.assertEqual(a, False)

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


class NamedExpressionScopeTest(unittest.TestCase):

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,8 @@ def test_named_expressions(self):
self.check_suite("foo(b := 2, a=1)")
self.check_suite("foo((b := 2), a=1)")
self.check_suite("foo(c=(b := 2), a=1)")
self.check_suite("{(x := C(i)).q: x for i in y}")


#
# Second, we take *invalid* trees and make sure we get ParserError
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Reverse evaluation order of key: value in dict comprehensions as proposed in PEP 572.
I.e. in ``{k: v for ...}``, ``k`` will be evaluated before ``v``.
4 changes: 2 additions & 2 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2944,8 +2944,8 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
}

case TARGET(MAP_ADD): {
PyObject *key = TOP();
PyObject *value = SECOND();
PyObject *value = TOP();
PyObject *key = SECOND();
PyObject *map;
int err;
STACK_SHRINK(2);
Expand Down
8 changes: 4 additions & 4 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -4238,10 +4238,10 @@ compiler_sync_comprehension_generator(struct compiler *c,
ADDOP_I(c, SET_ADD, gen_index + 1);
break;
case COMP_DICTCOMP:
/* With 'd[k] = v', v is evaluated before k, so we do
/* With '{k: v}', k is evaluated before v, so we do
the same. */
VISIT(c, expr, val);
VISIT(c, expr, elt);
VISIT(c, expr, val);
ADDOP_I(c, MAP_ADD, gen_index + 1);
break;
default:
Expand Down Expand Up @@ -4327,10 +4327,10 @@ compiler_async_comprehension_generator(struct compiler *c,
ADDOP_I(c, SET_ADD, gen_index + 1);
break;
case COMP_DICTCOMP:
/* With 'd[k] = v', v is evaluated before k, so we do
/* With '{k: v}', k is evaluated before v, so we do
the same. */
VISIT(c, expr, val);
VISIT(c, expr, elt);
VISIT(c, expr, val);
ADDOP_I(c, MAP_ADD, gen_index + 1);
break;
default:
Expand Down