What is the order of evaluation in python when using pop(), list[-1] and +=?

后端 未结 5 867
感情败类
感情败类 2021-02-05 00:48
a = [1, 2, 3]
a[-1] += a.pop()

This results in [1, 6].

a = [1, 2, 3]
a[0] += a.pop()

This results in

相关标签:
5条回答
  • 2021-02-05 01:05

    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() returns 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.

    0 讨论(0)
  • 2021-02-05 01:10

    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.

    0 讨论(0)
  • 2021-02-05 01:11

    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]
    
    0 讨论(0)
  • 2021-02-05 01:22

    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.

    0 讨论(0)
  • 2021-02-05 01:31

    For you specific example

    a[-1] += a.pop() #is the same as 
    a[-1] = a[-1] + a.pop() # a[-1] = 3 + 3
    

    Order:

    1. evaluate a[-1] after =
    2. pop(), decreasing the length of a
    3. addition
    4. assignment

    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

    1. evaluate a[0] after =
    2. pop()
    3. addition
    4. assignment

    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.

    0 讨论(0)
提交回复
热议问题