今日、私は興味をそそることを見つけ、誰かがその違いが何であるかに光を当てることができるかどうか疑問に思いましたか?
import numpy as np
A = np.arange(12).reshape(4,3)
for a in A:
a = a + 1
B = np.arange(12).reshape(4,3)
for b in B:
b += 1
各for
ループを実行した後、A
は変更されていませんが、B
は各要素に追加されています。私は実際にB
バージョンを使用して、for
ループ内の初期化されたNumPy配列に書き込みます。
違いは、一方がデータ構造自体を変更し(インプレース操作)b += 1
を、もう一方が再割り当てを変数a = a + 1
に変更することです。
完全を期して
x += y
は 常にではありません インプレース操作を行うと、(少なくとも)3つの例外があります。
x
が __iadd__
メソッドを実装していない場合、x += y
ステートメントはx = x + y
の単なる省略形です。これは、x
がint
のようなものである場合に当てはまります。
__iadd__
がNotImplemented
を返す場合、Pythonはx = x + y
にフォールバックします。
__iadd__
メソッドは理論的には正しく機能しないように実装することができます。しかし、それをするのは本当に奇妙なことです。
それが起こるときあなたのb
sはnumpy.ndarray
を実装してそれ自身を返す__iadd__
sであるのであなたの2番目のループは元の配列をその場で修正します。
これについての詳細は Pythonのドキュメント "Emulating Numeric Types" をご覧ください。
これら【
__i*__
】方法は、拡張演算割り当て(+=
、-=
、*=
、@=
、/=
、//=
、%=
、**=
、<<=
、>>=
、&=
、^=
、|=
)を実装するために呼び出されます。これらのメソッドは、操作をインプレースで実行し(selfを変更)、結果を返すようにします(これはselfである可能性がありますが、必ずしもそうである必要はありません)。特定のメソッドが定義されていない場合、拡張割り当ては通常のメソッドに戻ります。たとえば、xが__iadd__()
メソッドを持つクラスのインスタンスである場合、x += y
はx = x.__iadd__(y)
と同等です。それ以外の場合は、x + y
の評価と同様に、x.__add__(y)
とy.__radd__(x)
が考慮されます。特定の状況では、代入を拡張すると予期しないエラーが発生する可能性があります( 加算が有効なときにa_Tuple[i] += ["item"]
が例外を発生させる理由は? を参照)。
最初の例では、変数a
を再割り当てしていますが、2番目の例では、+=
演算子を使用してインプレースでデータを変更しています。
7.2.1に関するセクションを参照してください。拡張割り当てステートメント :
x += 1
のような拡張された代入式は、x = x + 1
のように書き直すことができますが、まったく同じ効果は得られません。拡張バージョンでは、xは一度だけ評価されます。 また、可能であれば、実際の操作はインプレースで実行されます 。つまり、新しいオブジェクトを作成してターゲットに割り当てるのではなく、古いオブジェクトが変更されます。
+=
演算子は __iadd__
を呼び出します。この関数は変更をインプレースで行い、その実行後に初めて、結果は+=
を "適用"しているオブジェクトに戻されます。
一方、 __add__
はパラメータを取得し、それらの合計を返します(変更することなく)。
すでに指摘したように、b += 1
はb
name__をインプレースで更新し、a = a + 1
はa + 1
を計算してから結果にa
name__という名前を割り当てます(現在はa
name__はA
name__の行を参照していません)。
+=
演算子を正しく理解するためには、mutableとimmutableオブジェクトの概念も理解する必要があります。 .reshape
を省略するとどうなるか考えてみましょう。
C = np.arange(12)
for c in C:
c += 1
print(C) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
C
name__はnot更新されています。つまり、c += 1
とc = c + 1
は同等です。これは、C
name__が1D配列(C.ndim == 1
)であるため、C
name__を反復処理するときに、各整数要素が引き出されてc
name__に割り当てられるためです。
現在のPythonでは、整数は不変です。つまり、インプレース更新は許可されていません。つまり、c += 1
をc = c + 1
に効果的に変換できます。ここで、c
name__はnew整数を指し、C
name__とは結合しません。再整形された配列をループすると、行全体(np.ndarray
)が一度にb
name__(およびa
name__)に割り当てられます。これは、mutableオブジェクトです。つまり、自由に新しい整数に固定できます。 a += 1
を実行すると起こります。
+
と+=
は上記のように関連することを意図していますが(通常はそうですが)、どんな型でもそれぞれ__add__
と __iadd__
メソッドを定義することによって望みの方法でそれらを実装できます。
短い形式(a += 1
)には、合計を表す新しいオブジェクトを作成して同じ名前(a = a + 1
)に再バインドする代わりに、a
in-placeを変更するオプションがあります。 、短い形式(a += 1
)は、a = a + 1
とは異なり、a
のコピーを作成する必要がないため、非常に効率的です。
また、同じ結果を出力している場合でも、+
と+=
という別個の演算子であるため、異なることに注意してください。
最初に:ループ内の変数aとbはnumpy.ndarray
オブジェクトを参照しています。
最初のループでは、a = a + 1
は次のように評価されます。numpy.ndarray
の__add__(self, other)
関数が呼び出されます。これにより新しいオブジェクトが作成されるため、Aは変更されません。その後、結果を参照するように変数a
が設定されます。
2番目のループでは、新しいオブジェクトは作成されません。ステートメントb += 1
は、bが参照している場所でndarray
オブジェクトを変更するnumpy.ndarray
の__iadd__(self, other)
関数を呼び出します。したがって、B
は修正されます。