a = [1, 2, 3]
a[-1] += a.pop()
This results in [1, 6]
.
a = [1, 2, 3]
a[0] += a.pop()
This results in [4, 2]
. What order of evaluation gives these two results?
a = [1, 2, 3]
a[-1] += a.pop()
This results in [1, 6]
.
a = [1, 2, 3]
a[0] += a.pop()
This results in [4, 2]
. What order of evaluation gives these two results?
RHS first and then LHS. And at any side, the evaluation order is left to right.
a[-1] += a.pop()
is same as, a[-1] = a[-1] + a.pop()
a = [1,2,3]
a[-1] = a[-1] + a.pop() # a = [1, 6]
See how the behavior changes when we change the order of the operations at RHS,
a = [1,2,3]
a[-1] = a.pop() + a[-1] # a = [1, 5]
f() + g() * h()
, the functions are evaluated in order f, then g, then h. For instance, a.pop() + a.pop() * a.pop()
with a = [3, 2, 1]
yields 7
(1 + 2 * 3) –
Borisborja a[-1]
is evaluated before the value is popped off, giving a value of 3
. Then the pop
occurs also giving a value of 3
. Thus the math is 3+3
. This is because on the RHS we work left-to-right evaluating each part by itself then dealing with the math. –
Engross +
will be done first, but that doesn't mean d
has to be evaluated first. –
Plyler The key insight is that a[-1] += a.pop()
is syntactic sugar for a[-1] = a[-1] + a.pop()
. This holds true because +=
is being applied to an immutable object (an int
here) rather than a mutable object (relevant question here).
The right hand side (RHS) is evaluated first. On the RHS: equivalent syntax is a[-1] + a.pop()
. First, a[-1]
gets the last value 3
. Second, a.pop()
return
s 3
.
3
+ 3
is 6
.
On the Left hand side (LHS), a
is now [1,2]
due to the in-place mutation already applied by list.pop()
and so the value of a[-1]
is changed from 2
to 6
.
a
is a list of ints, I have now learned. It's worth mentioning. –
Alban a
was a list of strings or tuples, it would also behave the same as the integer case (it's only different for mutable objects) –
Ento Let's have a look at the output of dis.dis
for a[-1] += a.pop()
1):
3 15 LOAD_FAST 0 (a) # a,
18 LOAD_CONST 5 (-1) # a, -1
21 DUP_TOP_TWO # a, -1, a, -1
22 BINARY_SUBSCR # a, -1, 3
23 LOAD_FAST 0 (a) # a, -1, 3, a
26 LOAD_ATTR 0 (pop) # a, -1, 3, a.pop
29 CALL_FUNCTION 0 (0 positional, 0 keyword pair) # a, -1, 3, 3
32 INPLACE_ADD # a, -1, 6
33 ROT_THREE # 6, a, -1
34 STORE_SUBSCR # (empty)
The meaning of the different instructions is listed here.
First, LOAD_FAST
and LOAD_CONST
load a
and -1
onto the stack, and DUP_TOP_TWO
duplicates the two, before BINARY_SUBSCR
gets the subscript value, resulting in a, -1, 3
on the stack. It then loads a
again, and LOAD_ATTR
loads the pop
function, which is called with no arguments by CALL_FUNCTION
. The stack is now a, -1, 3, 3
, and INPLACE_ADD
adds the top two values. Finally, ROT_THREE
rotates the stack to 6, a, -1
to match the order expected by STORE_SUBSCR
and the value is stored.
So, in short, the current value of a[-1]
is evaluated before calling a.pop()
and the result of the addition is then stored back to the new a[-1]
, irrespective of its current value.
1) This is the disassembly for Python 3, slightly compressed to better fit on the page, with an added column showing the stack after # ...
; for Python 2 it looks a bit different, but similar.
dis.dis
, didn't know this one! –
Nonet Using a thin wrapper around a list with debugging print-statements can be used to show the order of evaluation in your cases:
class Test(object):
def __init__(self, lst):
self.lst = lst
def __getitem__(self, item):
print('in getitem', self.lst, item)
return self.lst[item]
def __setitem__(self, item, value):
print('in setitem', self.lst, item, value)
self.lst[item] = value
def pop(self):
item = self.lst.pop()
print('in pop, returning', item)
return item
When I now run your example:
>>> a = Test([1, 2, 3])
>>> a[-1] += a.pop()
in getitem [1, 2, 3] -1
in pop, returning 3
in setitem [1, 2] -1 6
So it starts by getting the last item, which is 3, then pops the last item which is also 3, adds them and overwrites the last item of your list with 6
. So the final list will be [1, 6]
.
And in your second case:
>>> a = Test([1, 2, 3])
>>> a[0] += a.pop()
in getitem [1, 2, 3] 0
in pop, returning 3
in setitem [1, 2] 0 4
This now takes the first item (1
) adds it to the popped value (3
) and overwrites the first item with the sum: [4, 2]
.
The general order of evaluation is already explained by @Fallen
and @tobias_k
. This answer just supplements the general principle mentioned there.
For you specific example
a[-1] += a.pop() #is the same as
a[-1] = a[-1] + a.pop() # a[-1] = 3 + 3
Order:
a[-1]
after =
pop()
, decreasing the length of a
The thing is, that a[-1]
becomes the value of a[1]
(was a[2]
) after the pop()
, but this happens before the assignment.
a[0] = a[0] + a.pop()
Works as expected
a[0]
after =
pop()
This example shows, why you shouldn't manipulate a list while working on it (commonly said for loops). Always work on copys in this case.
© 2022 - 2024 — McMap. All rights reserved.
a[-1] += a.pop()
toa[-1] = a[-1] + a.pop()
. That is why you get the6
.a[-1]
is evaluated before thea.pop()
. If you change it toa[-1] = a.pop() + a[-1]
you get5
– Chiastolitea += b
is not the same asa = a + b
and is not guaranteed to be translated as such by Python. Different methods are invoked. Whether or it boils down too the same thing depends on the types of the operands. – Morgun+
calls the__add__
method whereas+=
tries to call the__iadd__
method - these might have the same effect (e.g. integers) or they might be slightly different (e.g. lists). – Morgun+
and+=
operators are equivalent for integers is key to answering this question: the general result would be the same if different objects were involved. I think the evaluation order is the main point.) – Morgunb = [[5], [3]]; a = b[1]; b[-1] += b.pop(); print (a)
is even more confusing! – Albana = [1, 2, 3]; temp = a.pop(); a[-1] = 2 * temp
ora = [1, 2, 3]; temp = a.pop(); a[-1] += temp
, depending on what you meant to do. This makes your intended order of evaluation explicit, and it's easier to get right. – Sonjasonnet