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
This commit is contained in:
parent
bb110cc2ed
commit
c8a35417db
|
@ -645,10 +645,12 @@ the original TOS1.
|
||||||
|
|
||||||
.. opcode:: MAP_ADD (i)
|
.. 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.
|
comprehensions.
|
||||||
|
|
||||||
.. versionadded:: 3.1
|
.. 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`
|
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
|
instructions, while the added value or key/value pair is popped off, the
|
||||||
|
|
|
@ -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
|
datum (textually rightmost in the display) stored for a given key value
|
||||||
prevails.
|
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:
|
.. _genexpr:
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,35 @@ class DictComprehensionTest(unittest.TestCase):
|
||||||
compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "<test>",
|
compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "<test>",
|
||||||
"exec")
|
"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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -212,6 +212,11 @@ class NamedExpressionAssignmentTest(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(a, False)
|
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):
|
class NamedExpressionScopeTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -473,6 +473,8 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase):
|
||||||
self.check_suite("foo(b := 2, a=1)")
|
self.check_suite("foo(b := 2, a=1)")
|
||||||
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("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
|
# Second, we take *invalid* trees and make sure we get ParserError
|
||||||
|
|
|
@ -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``.
|
|
@ -2940,8 +2940,8 @@ main_loop:
|
||||||
}
|
}
|
||||||
|
|
||||||
case TARGET(MAP_ADD): {
|
case TARGET(MAP_ADD): {
|
||||||
PyObject *key = TOP();
|
PyObject *value = TOP();
|
||||||
PyObject *value = SECOND();
|
PyObject *key = SECOND();
|
||||||
PyObject *map;
|
PyObject *map;
|
||||||
int err;
|
int err;
|
||||||
STACK_SHRINK(2);
|
STACK_SHRINK(2);
|
||||||
|
|
|
@ -4238,10 +4238,10 @@ compiler_sync_comprehension_generator(struct compiler *c,
|
||||||
ADDOP_I(c, SET_ADD, gen_index + 1);
|
ADDOP_I(c, SET_ADD, gen_index + 1);
|
||||||
break;
|
break;
|
||||||
case COMP_DICTCOMP:
|
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. */
|
the same. */
|
||||||
VISIT(c, expr, val);
|
|
||||||
VISIT(c, expr, elt);
|
VISIT(c, expr, elt);
|
||||||
|
VISIT(c, expr, val);
|
||||||
ADDOP_I(c, MAP_ADD, gen_index + 1);
|
ADDOP_I(c, MAP_ADD, gen_index + 1);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -4327,10 +4327,10 @@ compiler_async_comprehension_generator(struct compiler *c,
|
||||||
ADDOP_I(c, SET_ADD, gen_index + 1);
|
ADDOP_I(c, SET_ADD, gen_index + 1);
|
||||||
break;
|
break;
|
||||||
case COMP_DICTCOMP:
|
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. */
|
the same. */
|
||||||
VISIT(c, expr, val);
|
|
||||||
VISIT(c, expr, elt);
|
VISIT(c, expr, elt);
|
||||||
|
VISIT(c, expr, val);
|
||||||
ADDOP_I(c, MAP_ADD, gen_index + 1);
|
ADDOP_I(c, MAP_ADD, gen_index + 1);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
Loading…
Reference in New Issue