「i + = x」はPythonの「i = i + x」といつ違うのですか?


212

+=の標準表記とは異なる効果を持つ可能性があると言われましたi = i +i += 1と違うケースはありi = i + 1ますか?


7
+=extend()リストの場合と同様に機能します。
Ashwini Chaudhary 2013年

12
@AshwiniChaudharyそれを考えると、かなり微妙な違いi=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]ですTrue。多くの開発者はid(i)、一方の操作の変更に気づかないかもしれませんが、もう一方の変更には気づきません。
kojiro

1
@kojiro-微妙な違いですが、重要なものだと思います。
mgilson 2013年

@mgilsonは重要なので、説明が必要だと感じました。:)
kojiro

1
Javaの2つの違いに関する関連質問:stackoverflow.com/a/7456548/245966
jakub.g '19年

回答:


317

これは完全にオブジェクトに依存しますi

+=__iadd__メソッドを呼び出します(存在する場合- __add__存在しない場合はフォールバックします)一方+で、__add__メソッド1または__radd__いくつかのケースでメソッド2を呼び出します。

APIの観点から、__iadd__変更可能なオブジェクトを変更するために使用されることになっている場所で、一方、(変異したオブジェクトを返す)__add__返す必要があり、新しいインスタンス何かのを。不変オブジェクト、両方の方法は、新しいインスタンスを返しますが、__iadd__古いインスタンスが持っていたのと同じ名前で、現在の名前空間に新しいインスタンスを配置します。これが理由です

i = 1
i += 1

増加するようiです。実際には、新しい整数を取得して「上」に割り当てiます。古い整数への参照が1つ失われます。この場合、i += 1はとまったく同じi = i + 1です。しかし、ほとんどの可変オブジェクトでは、それは別の話です。

具体的な例として:

a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a  #[1, 2, 3, 1, 2, 3]
print b  #[1, 2, 3, 1, 2, 3]

に比べ:

a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]

最初の例では、同じオブジェクトba参照しているため、私が+=on を使用するbと実際に変更されますb(そしてa、その変更もわかります-結局、同じリストを参照しています)。ただし、2番目のケースではb = b + [1, 2, 3]、これを行うと、b参照しているリストを取得して、新しいリストに連結します[1, 2, 3]。その後、現在のネームスペースで連結されたリストを保存するb-何のためノーに関してはb前のラインでした。


1式においてはx + y、IFがx.__add__実装されていない場合、またはx.__add__(y)リターンNotImplemented xとはy異なるタイプを有する、その後x + yコールしようy.__radd__(x)。だから、あなたが持っている場合

foo_instance += bar_instance

Fooが実装されていない場合、__add__または__iadd__結果はここと同じです

foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

2式ではfoo_instance + bar_instancebar_instance.__radd__前に試みされfoo_instance.__add__ た場合のタイプbar_instanceのタイプのサブクラスであるfoo_instance(例えば、issubclass(Bar, Foo))。これが合理的であるのBarは、ある意味で「高レベル」オブジェクトは、Fooその動作Barをオーバーライドするオプションを取得する必要があるためFooです。


18
それが存在する場合+=呼び出し__iadd__ 、それ以外の場合は追加と再バインドにフォールバックします。そのためi = 1; i += 1、がない場合でも機能しますint.__iadd__。しかし、そのマイナーニット以外、素晴らしい説明。
abarnert 2013年

4
@abarnert-呼び出されたint.__iadd__ばかりだといつも思っていました__add__。今日は何か新しいことを学んでよかったです:)。
mgilson 2013年

@abarnert -私は多分すると仮定完全にx + y通話y.__radd__(x)の場合は、x.__add__存在して(または戻っていないNotImplementedxし、yさまざまなタイプがある)
mgilson

本当に完全になりたい場合は、「存在する場合」ビットが通常のgetattrメカニズムを通過することを言及する必要があります。ただし、クラシッククラスのいくつかの奇妙な点と、C APIに実装された型については、代わりにどちらかを探します。nb_inplace_addまたはsq_inplace_concat、およびそれらのC API関数には、Python dunderメソッドよりも厳しい要件があります。しかし、それは答えに関連するとは思いません。主な違いは、すでに説明+=したように+、のように動作するようにフォールバックする前に、インプレース追加を行おうとすることです。
abarnert 2013年

ええ、私はあなたが正しいと思います... C APIはpythonの一部ではないという立場に後退することはできますが。それの一部はCPython :-P
mgilson

67

カバーの下で、i += 1このようなことをします:

try:
    i = i.__iadd__(1)
except AttributeError:
    i = i.__add__(1)

一方では、i = i + 1このような何かを行います。

i = i.__add__(1)

これは少し単純化しすぎていますが、あなたはアイデアを得ます:Pythonは+=__iadd__メソッドとを作成することにより、型に特別に処理する方法を提供します__add__

のような不変の型はlistそれ自体を実装しないのに対し、などの可変の型は自分自身を変異させ__iadd__(そして、self非常にトリッキーなことを行わない限り、を返す)ことを意図しintています。

例えば:

>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]

はとl2同じオブジェクトl1であり、あなたが変異したl1ので、あなたも変異しましたl2

だが:

>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]

ここでは、突然変異しませんでしたl1。代わりに、新しいリストを作成しl1 + [3]、名前l1を再バインドしてそれをl2指すようにし、元のリストを指すようにしました。

+=バージョンでは、あなたも再バインドしl1ていましたが、その場合listはすでにバインドされているものに再バインドしているだけなので、通常はその部分を無視できます。)


__iadd__実際に呼び出す__add__のイベントにAttributeError
mgilson 2013年

まあ、i.__iadd__呼び出さないでください__add__。それi += 1が呼び出すこと__add__です。
abarnert 2013年

errrr ...うん、それは私が意味したことです。面白い。それが自動的に行われることに気づきませんでした。
mgilson 2013年

3
最初の試みは実際にはi = i.__iadd__(1)- オブジェクトを適切に変更iadd できますが、そうする必要はなく、どちらの場合でも結果を返すことが期待されています。
lvc

これはがをoperator.iadd呼び出すことを意味します__add__AttributeError、結果を再バインドすることはできません。そのため、i=1; operator.iadd(i, 1)2を返し、にi設定したままにし1ます。これは少し混乱します。
abarnert 2013年

6

ここでは、直接比較例であるi += xとはi = i + x

def foo(x):
  x = x + [42]

def bar(x):
  x += [42]

c = [27]
foo(c); # c is not changed
bar(c); # c is changed to [27, 42]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.