Pythonのa-= bとa = a-bの違い


90

私は最近、このソリューションをN行ごとの行列の平均に適用しました。ソリューションは一般的に機能しますが、7x1アレイに適用すると問題が発生しました。-=オペレーター使用時の問題だと気づきました。小さな例を作るには:

import numpy as np

a = np.array([1,2,3])
b = np.copy(a)

a[1:] -= a[:-1]
b[1:] = b[1:] - b[:-1]

print a
print b

出力:

[1 1 2]
[1 1 1]

したがって、配列の場合、とはa -= b異なる結果が生成されa = a - bます。これまでは、この2つの方法はまったく同じだと思っていました。違いはなんですか?

マトリックスのN行ごとに合計するために言及している方法が、たとえば7x4マトリックスでは機能するが7x1アレイでは機能しないのはなぜですか?

回答:


80

注:バージョン1.13.0以降では、メモリを共有するNumPy配列でインプレース操作を使用しても問題は発生しません(詳細はこちら)。2つの操作は同じ結果になります。この回答は、NumPyの以前のバージョンにのみ適用されます。


計算で使用されている配列を変更すると、予期しない結果が生じる可能性があります。

質問の例では、with -=を減算すると、の2番目の要素が変更され、aその変更された 2番目の要素がの3番目の要素の演算ですぐに使用さaます。

a[1:] -= a[:-1]ステップバイステップで何が起こるかはここにあります:

  • aデータを含む配列[1, 2, 3]です。

  • このデータにa[1:][2, 3]a[:-1]is とisの2つのビューがあり[1, 2]ます。

  • インプレース減算-=が始まります。の最初の要素でa[:-1]ある1は、の最初の要素から減算されa[1:]ます。これはに変更されaました[1, 1, 3]。今、私たちはそれが持っているa[1:]データの図であり[1, 3]、かつa[:-1]データの図である[1, 1](配列の2番目の要素はa変更されています)。

  • a[:-1]これ[1, 1]でNumPyは、2番目の要素から1(もう2ではない!)を2番目の要素から減算する必要がありますa[1:]。これによりa[1:]、値が表示されます[1, 2]

  • aは値を持つ配列になりました[1, 1, 2]

b[1:] = b[1:] - b[:-1]最初に新しい配列をb[1:] - b[:-1]作成してから、この配列の値をに割り当てるため、この問題は発生しません。それは変更されませんビューので、減算時に自分自身をし、変更しないでください。b[1:]bb[1:]b[:-1]


一般的なアドバイスは、ビューが重複している場合に、ビューを別のビューで変更しないようにすることです。これは、オペレータを含む-=*=使用等、およびout(のような汎用関数のパラメータをnp.subtractし、np.multiplyアレイの1つに書き戻します)。


4
私はこの答えを現在受け入れられているものよりも好みます。非常に明確な言語を使用して、変更可能なオブジェクトを変更した場合の影響を示します。さらに重要なことに、最後の段落では、重複するビューのインプレース変更の重要性を直接強調しています。これは、この質問から学ぶべき教訓です。
Reti43

43

内部的に、違いはこれです:

a[1:] -= a[:-1]

これと同等です:

a[1:] = a[1:].__isub__(a[:-1])
a.__setitem__(slice(1, None, None), a.__getitem__(slice(1, None, None)).__isub__(a.__getitem__(slice(1, None, None)))

この間:

b[1:] = b[1:] - b[:-1]

これにマップ:

b[1:] = b[1:].__sub__(b[:-1])
b.__setitem__(slice(1, None, None), b.__getitem__(slice(1, None, None)).__sub__(b.__getitem__(slice(1, None, None)))

いくつかのケースでは、__sub__()__isub__()同様の方法で作業。ただし、可変オブジェクトは__isub__()、を使用すると変異して自身を返す必要がありますが、で新しいオブジェクトを返す必要があります__sub__()

numpyオブジェクトにスライス操作を適用すると、それらにビューが作成されるため、それらを使用すると、「元の」オブジェクトのメモリに直接アクセスできます。


11

ドキュメントは言う:

Pythonでの拡張代入の背後にある考え方は、2項演算の結果を左側のオペランドに格納する一般的な方法を記述する簡単な方法だけでなく、問題の左側のオペランドがそれ自体の変更されたコピーを作成するのではなく、「それ自体」で動作する必要があることを知っています。

親指のルールとして、拡張substraction( x-=y)であるx.__isub__(y)ため、INインプレース操作 IF正常substractionは(可能なx = x-y)ですx=x.__sub__(y)。整数のような変更できないオブジェクトでは同等です。しかし、例のように、配列やリストなどの変更可能なものでは、非常に異なるものになる可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.