classification
Title: Incorrect evaluation order of function arguments with *args
Type: behavior Stage: needs patch
Components: Interpreter Core Versions: Python 3.5, Python 3.4
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Joshua.Landau, NeilGirdhar, SilentGhost, r.david.murray, terry.reedy
Priority: low Keywords:

Created on 2015-01-25 15:46 by Joshua.Landau, last changed 2015-01-31 07:32 by terry.reedy.

Messages (11)
msg234672 - (view) Author: Joshua Landau (Joshua.Landau) * Date: 2015-01-25 15:46
It is claimed that all expressions are evaluated left-to-right, including in functions¹. However,

    f(*a(), b=b())

will evaluate b() before a().

¹ https://docs.python.org/3/reference/expressions.html#evaluation-order
msg234673 - (view) Author: SilentGhost (SilentGhost) * (Python triager) Date: 2015-01-25 16:28
It seems, if I read https://docs.python.org/3/reference/expressions.html#calls correctly that the evaluation order of the function arguments is not defined in general, as it depends on your use of keyword argument and exact function signature. Naturally, f(a(), b()) would be evaluated in the order you're expecting.
msg234675 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-01-25 16:47
The resolution of issue 16967 argues that this should probably be considered a bug.  It certainly goes against normal Python expectations.  I think it should also be considered to be of low priority, though.
msg234697 - (view) Author: Neil Girdhar (NeilGirdhar) * Date: 2015-01-25 22:20
I assume this is the problem:

>>> dis.dis('f(*a(), b=b())')
  1           0 LOAD_NAME                0 (f)
              3 LOAD_NAME                1 (a)
              6 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
              9 LOAD_CONST               0 ('b')
             12 LOAD_NAME                2 (b)
             15 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             18 CALL_FUNCTION_VAR      256 (0 positional, 1 keyword pair)
             21 RETURN_VALUE

— looks fine.

>>> dis.dis('f(b=b(), *a())')
  1           0 LOAD_NAME                0 (f)
              3 LOAD_NAME                1 (a)
              6 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
              9 LOAD_CONST               0 ('b')
             12 LOAD_NAME                2 (b)
             15 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             18 CALL_FUNCTION_VAR      256 (0 positional, 1 keyword pair)
             21 RETURN_VALUE

Joshua, we could make function calls take:

x lists
y dictionaries
one optional list
z dictionaries

but we as well do all the merging in advance:

one optional list
one optional dictionary
one optional list
one optional dictionary

which is representable in three bits, but four is easier to decode I think.
msg234698 - (view) Author: Neil Girdhar (NeilGirdhar) * Date: 2015-01-25 22:28
actually, we accept alternation, so maybe a bit to say whether we start with a list or a dict followed by a length of the alternating sequence?
msg234702 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-01-25 22:38
Neil: I presume you are speaking of your in-progress PEP patch, and not the current python code?  If so, please keep the discussion of handling this in the context of the PEP to the PEP issue.  This issue should be for resolving the bug in the current code (if we choose to do so...if the PEP gets accepted for 3.5 this issue may become irrelevant, as I'm not sure we'd want to fix it in 3.4 for backward compatibility reasons).
msg234703 - (view) Author: Neil Girdhar (NeilGirdhar) * Date: 2015-01-25 22:50
Yes, sorry David.  I got linked here from the other issue.  In any case, in the current code, the longest alternating sequence possible is 4.  So one way to solve this is to change the CALL_FUNCTION parameters to be lists and dicts only and then process the final merging in CALL_FUNCTION.
msg234704 - (view) Author: Neil Girdhar (NeilGirdhar) * Date: 2015-01-25 23:02
another option is to add a LIST_EXTEND(stack_difference) opcode that would allow us to take the late iterable and extend a list at some arbitrary stack position.  I had to add something like that for dicts for the other issue, so it would follow that pattern.
msg234756 - (view) Author: Neil Girdhar (NeilGirdhar) * Date: 2015-01-26 16:43
After thinking about this a bit more, my suggestion is not to fix it.  Instead, I suggest that PEP 8 be modified to suggest that all positional arguments and iterable argument unpackings precede keyword arguments and keyword argument unpackings.  Then, a tool like autopep8 is free to reorganize argument lists.  Such reorganization will not be possible if f(*a(), b=b()) is different than f(b=b(), *a()).
msg234773 - (view) Author: Neil Girdhar (NeilGirdhar) * Date: 2015-01-26 19:41
(I also suggest that the evaluation order within a function argument list to be defined to be positional and iterable before keyword, otherwise left-to-right — rather than strictly left-to-right).
msg235083 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2015-01-31 07:32
I expect left to right as documented (and designed by Guido).  His OK or clarification would be to intentionally do differently.
History
Date User Action Args
2015-01-31 07:32:21terry.reedysetnosy: + terry.reedy
messages: + msg235083
2015-01-26 19:41:26NeilGirdharsetmessages: + msg234773
2015-01-26 16:43:30NeilGirdharsetmessages: + msg234756
2015-01-25 23:02:42NeilGirdharsetmessages: + msg234704
2015-01-25 22:50:56NeilGirdharsetmessages: + msg234703
2015-01-25 22:38:47r.david.murraysetmessages: + msg234702
2015-01-25 22:28:14NeilGirdharsetmessages: + msg234698
2015-01-25 22:20:36NeilGirdharsetnosy: + NeilGirdhar
messages: + msg234697
2015-01-25 16:47:43r.david.murraysetpriority: normal -> low

nosy: + r.david.murray
messages: + msg234675

stage: needs patch
2015-01-25 16:28:49SilentGhostsetnosy: + SilentGhost
messages: + msg234673
2015-01-25 15:46:56Joshua.Landaucreate