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 3
。 3
+ 3
是6
。
在左侧(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_FAST
和LOAD_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
订购:
- 在
=
之后评估a[-1]
-
pop()
,减lessa的长度 - 加成
- 分配
事情是, a[1]
在pop()
之后变成a[1]
(是a[2]
)的值,但是这发生在赋值之前。
a[0] = a[0] + a.pop()
按预期工作
- 在
=
之后评估a[0]
-
pop()
- 加成
- 分配
这个例子显示了为什么你不应该在处理它的时候操作一个列表(通常被称为for循环)。 在这种情况下,总是在copys上工作。