a = [1, 2, 3]
a[-1] += a.pop()
これは[1, 6]
になります。
a = [1, 2, 3]
a[0] += a.pop()
これは[4, 2]
になります。これらの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)では、a
は_[1,2]
_になりました。これは、list.pop()
によってすでに適用されているインプレース変異により、_a[-1]
_の値が_2
_から_6
_に変更されました。
_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)
_
さまざまな指示の意味をリストします ここ 。
最初に、_LOAD_FAST
_と_LOAD_CONST
_がa
と_-1
_をスタックにロードし、_DUP_TOP_TWO
_が2つを複製してから、_BINARY_SUBSCR
_が添え字値を取得します、その結果、スタックに_a, -1, 3
_が生成されます。次にa
を再度ロードし、_LOAD_ATTR
_はpop
関数をロードします。これは_CALL_FUNCTION
_によって引数なしで呼び出されます。スタックは_a, -1, 3, 3
_になり、_INPLACE_ADD
_は上位2つの値を追加します。最後に、_ROT_THREE
_は、_6, a, -1
_が予期する順序と一致するようにスタックを_STORE_SUBSCR
_にローテーションし、値が格納されます。
つまり、要するに、_a[-1]
_の現在の値はa.pop()
を呼び出す前に評価され、加算の結果は現在の値に関係なく新しい_a[-1]
_に格納されます。
1) これはPython 3の逆アセンブリです。ページに収まるように少し圧縮されており、_# ...
_の後にスタックを示す列が追加されています。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]
になります。
そしてあなたの2番目のケースでは:
>>> 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]
で上書きされます。
評価の一般的な順序は、すでに @Fallen
および @tobias_k
で説明されています。この回答は、そこで言及されている一般的な原則を補足するだけです。
あなたのための具体的な例
_a[-1] += a.pop() #is the same as
a[-1] = a[-1] + a.pop() # a[-1] = 3 + 3
_
注文:
a[-1]
_の後に_=
_を評価するpop()
、a
の長さを減らしますつまり、_a[-1]
_はpop()
の後に_a[1]
_の値になります(以前は_a[2]
_)ですが、これは代入の前に行われます。
_a[0] = a[0] + a.pop()
_
期待どおりに動作します
a[0]
_の後に_=
_を評価するpop()
この例は、リストでの作業中にリストを操作してはならない理由を示しています(一般にforループと呼ばれます)。この場合は、常にコピーで作業してください。