This is something I tried out in ipython
the behavior is quite clear: when creating the dictionary in lines 3 and 6, the dictionaries are created as if invoked by dict(**kwargs)
and the kwargs are being processed left to right but is it a CPython implementation or is the behavior actually specified in Python's grammar?
Python 3.12.2 (v3.12.2:6abddd9f6a, Feb 6 2024, 17:02:06) [Clang 13.0.0 (clang-1300.0.29.30)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.22.2 -- An enhanced Interactive Python. Type '?' for help.
In [1]: x={"a":1, "b":2, "c":4}
In [2]: y= dict(z=x.pop("b"), **x)
In [3]: y
Out[3]: {'z': 2, 'a': 1, 'c': 4}
In [4]: x
Out[4]: {'a': 1, 'c': 4}
In [5]: x={"a":1, "b":2, "c":4}
In [6]: y= dict(**x, z=x.pop("b") )
In [7]: y
Out[7]: {'a': 1, 'b': 2, 'c': 4, 'z': 2}
In [8]: x
Out[8]: {'a': 1, 'c': 4}
The fact that arguments are evaluated from left to right is specified. For example, the evaluation order section of the documentation says that the subexpressions in
expr1(expr2, expr3, *expr4, **expr5)
are evaluated in the order suggested by their numeric suffixes.
However, it is not specified whether unpacking a *
- or **
-unpacked argument happens before evaluating later arguments. The details of unpacking order were even changed by accident in Python 3.9.
Thus, your first dict
call
dict(z=x.pop("b"), **x)
must evaluate x.pop("b")
before evaluating x
, and x
must of course be evaluated before it can be unpacked, so the behavior of this expression is guaranteed.
However, for your second dict
call:
dict(**x, z=x.pop("b") )
x
must be evaluated before x.pop("b")
, but there is no specified guarantee for whether it will be unpacked before evaluating x.pop("b")
, so the behavior of this expression is not guaranteed.
In fact, this expression is one of the cases impacted by the Python 3.9 changes, so if you try your code on Python 3.8:
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> x={"a":1, "b":2, "c":4}
>>> dict(**x, z=x.pop("b"))
{'a': 1, 'c': 4, 'z': 2}
you get different results.