30

Is it safe to assume that function arguments are evaluated from left to right in Python?

Reference states that it happens that way but perhaps there is some way to change this order which may break my code.

What I want to do is to add time stamp for function call:

l = []
l.append(f(), time.time())

I understand that I can evaluate the arguments sequentially:

l = []
res = f()
t = time.time()
l.append(res, t)

But it looks less elegant so I'd prefer the first way if I can rely on it.

vaultah
  • 44,105
  • 12
  • 114
  • 143
Phoenix
  • 751
  • 1
  • 7
  • 13
  • 7
    If the reference says so and it doesn't say explicitly that this may change in the future, then you can rely on it. – Fred Foo Jul 30 '13 at 13:29
  • `"some way to change this order"` How could it be changed? (Functions don't get a choice of how their arguments are evaluated, they're evaluated before they're ever passed to the function) – David Robinson Jul 30 '13 at 13:30
  • 1
    In your use case, evaluation order doesn't seem to matter, except if `f()` is a long running function. – Xaqq Jul 30 '13 at 13:33
  • 1
    @DavidRobinson As far as I remember in C you can't rely on that. Usually the arguments are evaluated form right to left according to __stdcall (or others) so that the order in the stack is convenient. I thought that perhaps in python there is a similar trick to change that order – Phoenix Jul 30 '13 at 13:35
  • @Xaqq It looks like the purpose of the code is to measure the time that `f()` takes. So it presumably does matter. – Barmar Jul 30 '13 at 13:36
  • @Barmar It looked to me that it was more to log the time of the call. But yeah, you may totally be right :) – Xaqq Jul 30 '13 at 13:38
  • @Xaqq The purpose is to measure the running time. For instance f() may invoke ssh to execute something on remote host. That may take some time and I want to measure it – Phoenix Jul 30 '13 at 13:39
  • 2
    I disagree that the sequential version is less elegant; it makes the dependency explicit. How many people who read your code will know that Python enforces order for sure? – japreiss Jul 30 '13 at 13:48

2 Answers2

47

Quoting from the reference documentation:

Python evaluates expressions from left to right.

So yes, you can count on that (with one exception, see below).

A call (the (...) part after a primary, such as a function name) is just another expression primary, and the arguments for the call are just more expressions.

Note: There is one exception to this rule. When using *expression in a call (to expand an iterable to form additional positional arguments), then this expression is evaluated before any keyword argument expressions:

>>> from itertools import count
>>> def bar(n, r=(), c=count()): print(f'{next(c)}: bar({n!r})'); return r
...
>>> def foo(*args, **kwargs): pass
...
>>> foo(bar('a1'), spam=bar('a2'), *bar('varargs'), **bar('kwargs', {}))
0: bar('a1')
1: bar('varargs')
2: bar('a2')
3: bar('kwargs')

The linked documentation states:

A consequence of this is that although the *expression syntax may appear after explicit keyword arguments, it is processed before the keyword arguments[.]

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 13
    This is a better answer than the other, pointing out where it can be found in the language reference. The other one just shows that it is this way, but it doesn't prove it is supposed to be always this way. – glglgl Jul 30 '13 at 15:05
14

Yes, Python always evaluates function arguments from left to right.

This goes for any comma seperated list as far as I know:

>>> from __future__ import print_function
>>> def f(x, y): pass
...
>>> f(print(1), print(2))
1
2
>>> [print(1), print(2)]
1
2
[None, None]
>>> {1:print(1), 2:print(2)}
1
2
{1: None, 2: None}
>>> def f(x=print(1), y=print(2)): pass
...
1
2
orlp
  • 112,504
  • 36
  • 218
  • 315