Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prove that parameter evaluation is "left to right" in Python?

For example, in JavaScript we could write a program like this:

var a = 1;
testFunction(++a, ++a, a);
function testFunction(x, y, z){
      document.writeln("<br />x = " + x);
      document.writeln("<br />y = " + y);
      document.writeln("<br />z = " + z);
}

and we would get an output:

x = 2
y = 3
z = 3

This implies that parameters are truly evaluated from left to right in JavaScript. In C we would get output

x = 3
y = 3
z = 3

I was wondering if we could do the same in Python or is it impossible since it's a pass by value reference language?

I've made a simple program but I don't think that proves anything:

x = 2
def f(x, y, z):
    print(x, y, z)

f(x*2, x*2, x**2)
print(x)
4 4 4
2

Python won't let me do any new assignment within the function parameter when I call it (for example f(x=4, x, x) or something like this).

like image 955
BugShotGG Avatar asked Oct 05 '12 15:10

BugShotGG


People also ask

Does Python read left to right?

In Python, the left operand is always evaluated before the right operand. That also applies to function arguments.

How do you check parameters of a function in Python?

You can use inspect. getargspec() to see what arguments are accepted, and any default values for keyword arguments. inspect. getargspec() should be considered deprecated in Python 3.

Which is evaluated first and or or Python?

In an expression, Python interpreter evaluates operators with higher precedence first. And, except the exponent operator (**) all other operators get evaluated from left to right.

Does order of parameters matter Python?

It doesn't matter what order they are given in the parameter so long as the arguments in the call expression match the correct variable. Either way won't matter to the output string.


4 Answers

>>> def f(x, y): pass
...
>>> f(print(1), print(2))
1
2
like image 83
Fred Foo Avatar answered Oct 20 '22 16:10

Fred Foo


Disassemble the function call.

>>> def foo():
...   bar(x+1, x+2, x+3)
... 
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (bar)
              3 LOAD_GLOBAL              1 (x)
              6 LOAD_CONST               1 (1)
              9 BINARY_ADD          
             10 LOAD_GLOBAL              1 (x)
             13 LOAD_CONST               2 (2)
             16 BINARY_ADD          
             17 LOAD_GLOBAL              1 (x)
             20 LOAD_CONST               3 (3)
             23 BINARY_ADD          
             24 CALL_FUNCTION            3
             27 POP_TOP             
             28 LOAD_CONST               0 (None)
             31 RETURN_VALUE        
like image 34
Ignacio Vazquez-Abrams Avatar answered Oct 20 '22 17:10

Ignacio Vazquez-Abrams


Using Python 3:

>>> a = []
>>> f = print(
    a.append(1), a[:],
    a.append(2), a[:],
    a.append(3), a[:]
)
None [1] None [1, 2] None [1, 2, 3]

Archive:

>>> a = []
>>> f = print(a.append(1), a, a.append(2), a, a.append(3), a)

Curiously enough (at first), this code produces:

None [1, 2, 3] None [1, 2, 3] None [1, 2, 3]

However, dis(f) makes this clearer:

>>> dis(f)

  1           0 LOAD_NAME                0 (print) #Loads the value of 'print' into memory. Precisely, the value is pushed to the TOS (Top of Stack)
    -->       3 LOAD_NAME                1 (a) #Loads the value of object 'a' 
              6 LOAD_ATTR                2 (append) #Loads the append attribute (in this case method)
              9 LOAD_CONST               0 (1) #Loads the constant 1
             12 CALL_FUNCTION            1 #a.append(1) is called
             15 LOAD_NAME                1 (a) #for print(...,a,...)
             18 LOAD_NAME                1 (a) #for the next a.append()
             21 LOAD_ATTR                2 (append) 
             24 LOAD_CONST               1 (2) 
             27 CALL_FUNCTION            1 #a.append(2)
             30 LOAD_NAME                1 (a) 
             33 LOAD_NAME                1 (a) 
             36 LOAD_ATTR                2 (append) 
             39 LOAD_CONST               2 (3) 
             42 CALL_FUNCTION            1 #a.append(3)
             45 LOAD_NAME                1 (a) #loads a to be used thrice by print
             48 CALL_FUNCTION            6 #calls print
             51 PRINT_EXPR                 #prints TOS and clears it
             52 LOAD_CONST               3 (None) #Loads None
             55 RETURN_VALUE             #Returns None

The output of dis(f) is what we expected - L-to-R evaluation. Essentially, this "discrepancy" is a consequence of print() being evaluated last. By then, the value of a has changed to [1, 2, 3] and the same final object is printed thrice.

If we replace a with a[:], we get the expected result.

like image 27
Anuj Gupta Avatar answered Oct 20 '22 16:10

Anuj Gupta


A custom class can help here:

class Tester(object):
    "test object to reveal left to right evaluation"
    def __init__(self, value):
        self.value = value
    def __add__(self, value):
        print("adding ", value)
        return Tester(self.value + value)
    def __repr__(self):
        return repr(self.value)

and when run:

--> t = Tester(7)
--> t
7
--> t = t + 7
adding  7
--> t
14
--> def blah(a, b, c):
...   print(a, b, c)
... 
--> blah(t+1, t+2, t+3)
adding  1
adding  2
adding  3
15 16 17
like image 45
Ethan Furman Avatar answered Oct 20 '22 17:10

Ethan Furman