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).
In Python, the left operand is always evaluated before the right operand. That also applies to function arguments.
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.
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.
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.
>>> def f(x, y): pass
...
>>> f(print(1), print(2))
1
2
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
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With