python中使用pop(),list 和+ =时的评估顺序是什么?

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

这导致[1, 6]

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

这导致[4, 2] 。 什么样的评价顺序给出了这两个结果?

先是RHS,然后是LHS。 在任何一方,评估顺序都是从左到右。

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] 

关键的见解是a[-1] += a.pop()a[-1] = a[-1] + a.pop()语法糖。 这是真实的,因为+=被应用于一个不可变对象(这里是一个int )而不是一个可变对象( 这里的相关问题 )。

右边(RHS)首先被评估。 在RHS上:等价的语法是a[-1] + a.pop() 。 首先, a[-1]得到最后的值3 。 其次, a.pop() return s 33 + 36

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

让我们来看看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) 

这里列出了不同指令的含义。

首先, LOAD_FASTLOAD_CONST a-1加载到堆栈上, DUP_TOP_TWO复制这两个,然后在BINARY_SUBSCR获取下标值之后,在堆栈上产生a, -1, 3 BINARY_SUBSCR 。 然后再加载一次, LOAD_ATTR加载pop函数,这个函数被CALL_FUNCTION调用,没有参数。 堆栈现在a, -1, 3, 3 INPLACE_ADD ,并且INPLACE_ADD添加了前两个值。 最后, ROT_THREE将堆栈旋转到6, a, -1以匹配STORE_SUBSCR预期的STORE_SUBSCR并存储该值。

因此,简而言之,在调用a.pop()之前计算a[-1]的当前值,然后将a.pop()的结果存储回新的a[-1] ,与当前值a.pop()


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

在包含debugging打印语句的列表中使用薄包装可以用来显示您的情况下的评估顺序:

 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,然后popup最后一个项目也是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 )将其添加到popup的值( 3 ),并用总和: [4, 2]覆盖第一个项目。


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

为你具体的例子

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

订购:

  1. =之后评估a[-1]
  2. pop() ,减lessa的长度
  3. 加成
  4. 分配

事情是, a[1]pop()之后变成a[1] (是a[2] )的值,但是这发生在赋值之前。

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

按预期工作

  1. =之后评估a[0]
  2. pop()
  3. 加成
  4. 分配

这个例子显示了为什么你不应该在处理它的时候操作一个列表(通常被称为for循环)。 在这种情况下,总是在copys上工作。