使用 pop()、list[-1] 和 += 时,python 中的求值顺序是什么?

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

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

这导致 [1, 6]

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

这导致 [4, 2]。这两个结果的评估顺序是什么?

关键的见解是 a[-1] += a.pop()a[-1] = a[-1] + a.pop() 的语法糖。这是正确的,因为 += 被应用于不可变对象(此处为 int)而不是可变对象(relevant question here)。

首先评估右侧 (RHS)。在 RHS 上:等效语法是 a[-1] + a.pop()。首先,a[-1] 获取最后一个值 3。二、a.pop()returns33 + 36.

在左侧 (LHS),a 现在是 [1,2],因为 list.pop() 已经应用了就地突变,因此 [=15= 的值] 从 2 更改为 6

给你具体的例子

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

订单:

  1. =
  2. 之后评估 a[-1]
  3. pop(),减少a
  4. 的长度
  5. 加法
  6. 作业

问题是,a[-1]pop() 之后成为 a[1] 的值(原为 a[2]),但这发生在赋值之前。

a[0] = a[0] + a.pop() 

按预期工作

  1. =
  2. 之后评估 a[0]
  3. pop()
  4. 加法
  5. 作业

这个例子说明了为什么你不应该在处理列表时操作它(通常称为 for 循环)。在这种情况下始终处理副本。

先是右轴,然后是左轴。在任何一侧,评估顺序都是从左到右。

a[-1] += a.pop() 等同于 a[-1] = a[-1] + a.pop()

a = [1,2,3]
a[-1] = a[-1] + a.pop() # a = [1, 6]

看看当我们改变 RHS 的操作顺序时行为如何变化,

a = [1,2,3]
a[-1] = a.pop() + a[-1] # a = [1, 5]

让我们看看 dis.dis 对于 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)

列出不同指令的含义here

首先,LOAD_FASTLOAD_CONSTa-1 加载到堆栈上,然后 DUP_TOP_TWO 将两者复制,然后再 BINARY_SUBSCR获取下标值,导致堆栈上出现 a, -1, 3。然后它再次加载 aLOAD_ATTR 加载 pop 函数,CALL_FUNCTION 不带参数调用该函数。堆栈现在是 a, -1, 3, 3,并且 INPLACE_ADD 将前两个值相加。最后,ROT_THREE 将堆栈旋转到 6, a, -1 以匹配 STORE_SUBSCR 期望的顺序并存储值。

所以,简而言之,a[-1] 的当前值在调用 a.pop() 之前计算,然后将加法的结果存储回新的 a[-1],而不管其当前值。


1) 这是 Python 3 的反汇编,稍微压缩以更好地适应页面,在 [=32= 之后添加了一列显示堆栈];对于 Python 2,它看起来有点不同,但相似。

在带有调试打印语句的列表周围使用薄包装可用于显示您的案例中的评估顺序:

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

当我现在运行你的例子:

>>> 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

所以它首先获取最后一项,即 3,然后弹出最后一项也是 3,添加它们并用 6 覆盖列表的最后一项。所以最终列表将是 [1, 6].

在你的第二种情况下:

>>> 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

现在将第一项 (1) 添加到弹出值 (3) 并用总和覆盖第一项:[4, 2].


and 已经解释了评估的一般顺序。这个答案只是补充那里提到的一般原则。