web-dev-qa-db-ja.com

ループでのi = i + 1とi + = 1の違いは何ですか?

今日、私は興味をそそることを見つけ、誰かがその違いが何であるかに光を当てることができるかどうか疑問に思いましたか?

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配列に書き込みます。

103
Adam Fjeldsted

違いは、一方がデータ構造自体を変更し(インプレース操作)b += 1を、もう一方が再割り当てを変数a = a + 1に変更することです。


完全を期して

x += y 常にではありません インプレース操作を行うと、(少なくとも)3つの例外があります。

  • x__iadd__メソッドを実装していない場合、x += yステートメントはx = x + yの単なる省略形です。これは、xintのようなものである場合に当てはまります。

  • __iadd__NotImplementedを返す場合、Pythonはx = x + yにフォールバックします。

  • __iadd__メソッドは理論的には正しく機能しないように実装することができます。しかし、それをするのは本当に奇妙なことです。

それが起こるときあなたのbsはnumpy.ndarrayを実装してそれ自身を返す__iadd__sであるのであなたの2番目のループは元の配列をその場で修正します。

これについての詳細は Pythonのドキュメント "Emulating Numeric Types" をご覧ください。

これら【__i*__】方法は、拡張演算割り当て(+=-=*=@=/=//=%=**=<<=>>=&=^=|=)を実装するために呼び出されます。これらのメソッドは、操作をインプレースで実行し(selfを変更)、結果を返すようにします(これはselfである可能性がありますが、必ずしもそうである必要はありません)。特定のメソッドが定義されていない場合、拡張割り当ては通常のメソッドに戻ります。たとえば、xが__iadd__()メソッドを持つクラスのインスタンスである場合、x += yx = x.__iadd__(y)と同等です。それ以外の場合は、x + yの評価と同様に、x.__add__(y)y.__radd__(x)が考慮されます。特定の状況では、代入を拡張すると予期しないエラーが発生する可能性があります( 加算が有効なときにa_Tuple[i] += ["item"]が例外を発生させる理由は? を参照)。

109
MSeifert

最初の例では、変数aを再割り当てしていますが、2番目の例では、+=演算子を使用してインプレースでデータを変更しています。

7.2.1に関するセクションを参照してください。拡張割り当てステートメント

x += 1のような拡張された代入式は、x = x + 1のように書き直すことができますが、まったく同じ効果は得られません。拡張バージョンでは、xは一度だけ評価されます。 また、可能であれば、実際の操作はインプレースで実行されます 。つまり、新しいオブジェクトを作成してターゲットに割り当てるのではなく、古いオブジェクトが変更されます。

+=演算子は __iadd__ を呼び出します。この関数は変更をインプレースで行い、その実行後に初めて、結果は+=を "適用"しているオブジェクトに戻されます。

一方、 __add__ はパラメータを取得し、それらの合計を返します(変更することなく)。

28
Maroun

すでに指摘したように、b += 1bname__をインプレースで更新し、a = a + 1a + 1を計算してから結果にaname__という名前を割り当てます(現在はaname__はAname__の行を参照していません)。

+=演算子を正しく理解するためには、mutableimmutableオブジェクトの概念も理解する必要があります。 .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]

Cname__はnot更新されています。つまり、c += 1c = c + 1は同等です。これは、Cname__が1D配列(C.ndim == 1)であるため、Cname__を反復処理するときに、各整数要素が引き出されてcname__に割り当てられるためです。

現在のPythonでは、整数は不変です。つまり、インプレース更新は許可されていません。つまり、c += 1c = c + 1に効果的に変換できます。ここで、cname__はnew整数を指し、Cname__とは結合しません。再整形された配列をループすると、行全体(np.ndarray)が一度にbname__(およびaname__)に割り当てられます。これは、mutableオブジェクトです。つまり、自由に新しい整数に固定できます。 a += 1を実行すると起こります。

++=は上記のように関連することを意図していますが(通常はそうですが)、どんな型でもそれぞれ__add____iadd__ メソッドを定義することによって望みの方法でそれらを実装できます。

13
jmd_dk

短い形式(a += 1)には、合計を表す新しいオブジェクトを作成して同じ名前(a = a + 1)に再バインドする代わりに、a in-placeを変更するオプションがあります。 、短い形式(a += 1)は、a = a + 1とは異なり、aのコピーを作成する必要がないため、非常に効率的です。

また、同じ結果を出力している場合でも、++=という別個の演算子であるため、異なることに注意してください。

4
Inconnu

最初に:ループ内の変数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は修正されます。

3
Andi Kleve