web-dev-qa-db-ja.com

pop()、list [-1]および+ =を使用する場合のpythonの評価の順序は何ですか?

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

これは[1, 6]になります。

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

これは[4, 2]になります。これらの2つの結果を与える評価の順序は何ですか?

39
felipa

最初に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]
38
Fallen

重要な洞察は、a[-1] += a.pop()a[-1] = a[-1] + a.pop()の構文糖であることです。 _+=_は、可変オブジェクトではなく不変オブジェクト(ここではint)に適用されるため、これは当てはまります( 関連する質問 )。

右側(RHS)が最初に評価されます。 RHSでは、同等の構文はa[-1] + a.pop()です。まず、_a[-1]_は最後の値_3_を取得します。次に、a.pop()returns _3_です。 _3_ + _3_は_6_です。

左側(LHS)では、aは_[1,2]_になりました。これは、list.pop()によってすでに適用されているインプレース変異により、_a[-1]_の値が_2_から_6_に変更されました。

22
Chris_Rands

_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少し異なりますが、似ています。

16
tobias_k

印刷ステートメントをデバッグするリストの薄いラッパーを使用すると、ケースでの評価の順序を示すことができます。

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 で説明されています。この回答は、そこで言及されている一般的な原則を補足するだけです。

6
MSeifert

あなたのための具体的な例

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

注文:

  1. _a[-1]_の後に_=_を評価する
  2. pop()aの長さを減らします
  3. 添加
  4. 割り当て

つまり、_a[-1]_はpop()の後に_a[1]_の値になります(以前は_a[2]_)ですが、これは代入の前に行われます。

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

期待どおりに動作します

  1. _a[0]_の後に_=_を評価する
  2. pop()
  3. 添加
  4. 割り当て

この例は、リストでの作業中にリストを操作してはならない理由を示しています(一般にforループと呼ばれます)。この場合は、常にコピーで作業してください。

4
ppasler