使用pop()、list[-1]和+;=时,python中的求值顺序是什么?
这将导致使用pop()、list[-1]和+;=时,python中的求值顺序是什么?,python,list,operator-precedence,indices,Python,List,Operator Precedence,Indices,这将导致[1,6] a = [1, 2, 3] a[-1] += a.pop() 这将导致[4,2]。什么样的评估顺序给出这两个结果?关键洞察是a[-1]+=a.pop()是a[-1]=a[-1]+a.pop()的语法糖。这是正确的,因为+=应用于不可变对象(此处为int),而不是可变对象() 首先评估右侧(RHS)。在RHS上:等效语法是a[-1]+a.pop()。首先,a[-1]获取最后一个值3。其次,a.pop()返回s3。 3+3是6 在左侧(LHS),a现在是[1,2],这是由于li
[1,6]
a = [1, 2, 3]
a[-1] += a.pop()
这将导致
[4,2]
。什么样的评估顺序给出这两个结果?关键洞察是a[-1]+=a.pop()
是a[-1]=a[-1]+a.pop()的语法糖。这是正确的,因为+=
应用于不可变对象(此处为int
),而不是可变对象()
首先评估右侧(RHS)。在RHS上:等效语法是a[-1]+a.pop()
。首先,a[-1]
获取最后一个值3
。其次,a.pop()
返回s3
。
3
+3
是6
在左侧(LHS),a
现在是[1,2]
,这是由于list.pop()
已经应用了原位突变,因此对于特定示例,a[-1]
的值从2
更改为6
a = [1, 2, 3]
a[0] += a.pop()
订单:
在=
pop()
,减少a的长度
加成
分配
问题是,a[-1]
在pop()之后成为a[1]
(wasa[2]
)的值,但这发生在赋值之前
a[-1] += a.pop() #is the same as
a[-1] = a[-1] + a.pop() # a[-1] = 3 + 3
工作如期进行
在=
pop()
加成
分配
这个例子说明了为什么在处理列表时不应该操作它(通常称为循环)。在这种情况下,请始终使用复印机。先使用RHS,然后使用LHS。在任何一侧,求值顺序是从左到右
a[-1]+=a.pop()
与,a[-1]=a[-1]+a.pop()
看看当我们改变RHS的操作顺序时行为是如何变化的
a = [1,2,3]
a[-1] = a[-1] + a.pop() # a = [1, 6]
让我们看看fora[-1]+=a.pop()
1)的输出:
下面列出了不同说明的含义
首先,LOAD\u FAST
和LOAD\u CONST
将a
和-1
加载到堆栈上,并在BINARY\u SUBSCR
获取下标值之前将两者复制,从而在堆栈上生成a,-1,3
。然后再次加载a
,然后LOAD_ATTR
加载pop
函数,该函数由CALL_函数
无参数调用。堆栈现在是a,-1,3,3
,并且INPLACE\u ADD
添加前两个值。最后,ROT\u THREE
将堆栈旋转到6,a,-1
,以匹配STORE\u SUBSCR
所期望的顺序,并存储值
因此,简而言之,a[-1]
的当前值在调用a.pop()
之前进行评估,然后将加法结果存储回新的a[-1]
,而不管其当前值如何
1) 这是Python3的反汇编,略微压缩以更好地适应页面,并在#…
之后添加了一列显示堆栈;对于Python 2,它看起来有点不同,但很相似。在带有调试打印语句的列表周围使用一个薄型包装器,可以用来显示案例中的求值顺序:
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)
当我现在运行您的示例时:
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
因此,它首先获取最后一项,即3,然后弹出最后一项,即3,添加它们并用6
覆盖列表中的最后一项。因此,最终的列表将是[1,6]
a = [1, 2, 3]
a[-1] += a.pop()
在第二种情况下:
>>> 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
现在将第一项(1
)添加到弹出的值(3
)中,并用总和覆盖第一项:[4,2]
和已经解释了评估的一般顺序。这个答案只是补充了这里提到的一般原则。@tobias_k以第一个例子为例。a、 pop()返回3并将a更改为[1,2]。现在计算a[-1]=a[-1]+3得到的是5,而不是6。@felipa我猜Python首先将a[-1]+=a.pop()
转换为a[-1]=a[-1]+a.pop()
。这就是为什么您会得到6
a[-1]
在a.pop()
之前计算。如果您将其更改为a[-1]=a.pop()+a[-1]
,您将得到5
,尽管此页面上的多条注释似乎表明了相反的情况,请注意,通常a+=b
与a=a+b
不同,并且不保证Python会将其翻译成这样。调用不同的方法。归根结底是不是同样的事情取决于操作数的类型。@Chris_Rands:我试着用我的话谨慎地说,这两个“一般”是不等价的,这取决于对象:我同意这在这里是合理的。(但另一方面,我不确定+
和+=
运算符对于整数是等价的这一事实是回答这个问题的关键:如果涉及不同的对象,一般结果是相同的。我认为评估顺序是主要点。)PSA:永远不要编写依赖于这些细节的代码。即使它能工作,也不是好代码。如果您在自己的代码行上隔离副作用(这相当于每行修改一个状态),那么读取和修改代码会容易得多。在这种情况下,您应该执行a=[1,2,3];temp=a.pop();a[-1]=2*temp
或a=[1,2,3];temp=a.pop();a[-1]+=temp
,具体取决于您打算做什么。这使您的预期计算顺序更加明确,而且更容易正确。使用dis.dis的酷东西,我不知道这个!“在RHS,它是从左到右的”有趣的事实:虽然运算符当然是按w.r.t.运算符优先级计算的,但实际表达式显然不是,例如在f()+g()*h()
中,fu
>>> 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
>>> 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