+=
演算子pythonはリスト上で予期せず動作しているようです。
class foo:
bar = []
def __init__(self,x):
self.bar += [x]
class foo2:
bar = []
def __init__(self,x):
self.bar = self.bar + [x]
f = foo(1)
g = foo(2)
print f.bar
print g.bar
f.bar += [3]
print f.bar
print g.bar
f.bar = f.bar + [4]
print f.bar
print g.bar
f = foo2(1)
g = foo2(2)
print f.bar
print g.bar
[〜#〜] output [〜#〜]
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
はクラスのすべてのインスタンスに影響するようですが、foo = foo + bar
は、私が物事の振る舞いを期待する方法で振る舞うようです。
+=
演算子は「複合代入演算子」と呼ばれます。
一般的な答えは、+=
は__iadd__
特殊メソッドを呼び出そうとし、それが利用できない場合は、代わりに__add__
を使用しようとします。したがって、問題はこれらの特別な方法の違いにあります。
__iadd__
特殊メソッドは、インプレース追加用です。つまり、操作対象のオブジェクトを変更します。 __add__
特殊メソッドは新しいオブジェクトを返し、標準の+
演算子にも使用されます。
そのため、+=
演算子が__iadd__
が定義されているオブジェクトで使用されると、オブジェクトはその場で変更されます。それ以外の場合は、代わりにプレーン__add__
を使用して新しいオブジェクトを返そうとします。
リストのような可変型では+=
がオブジェクトの値を変更しますが、タプル、文字列、整数などの不変型では代わりに新しいオブジェクトが返されます(a += b
はa = a + b
と同等になります)。
したがって、__iadd__
と__add__
の両方をサポートするタイプの場合、どちらを使用するかに注意する必要があります。 a += b
は__iadd__
を呼び出してa
を変更しますが、a = a + b
は新しいオブジェクトを作成してa
に割り当てます。それらは同じ操作ではありません!
>>> a1 = a2 = [1, 2]
>>> b1 = b2 = [1, 2]
>>> a1 += [3] # Uses __iadd__, modifies a1 in-place
>>> b1 = b1 + [3] # Uses __add__, creates new list, assigns it to b1
>>> a2
[1, 2, 3] # a1 and a2 are still the same list
>>> b2
[1, 2] # whereas only b1 was changed
不変の型(__iadd__
がない場合)a += b
とa = a + b
は同等です。これは、不変型で+=
を使用できるようにするものです。そうしないと、数字のような不変型で+=
を使用できないと考えるまで、奇妙な設計上の決定に思えます。
一般的なケースについては、 スコットグリフィスの答え を参照してください。ただし、あなたのようなリストを扱う場合、_+=
_演算子はsomeListObject.extend(iterableObject)
の省略形です。 extend()のドキュメント を参照してください。
extend
関数は、パラメーターのすべての要素をリストに追加します。
_foo += something
_を実行すると、リストfoo
を変更するため、名前foo
が指す参照は変更しませんが、リストオブジェクトは変更します直接。 _foo = foo + something
_を使用すると、実際にはnewリストを作成しています。
このサンプルコードで説明します。
_>>> l = []
>>> id(l)
13043192
>>> l += [3]
>>> id(l)
13043192
>>> l = l + [3]
>>> id(l)
13059216
_
新しいリストをl
に再割り当てすると、参照がどのように変化するかに注意してください。
bar
はインスタンス変数ではなくクラス変数であるため、インプレース変更はそのクラスのすべてのインスタンスに影響します。ただし、_self.bar
_を再定義する場合、インスタンスは他のクラスインスタンスに影響を与えることなく、個別のインスタンス変数_self.bar
_を持ちます。
ここでの問題は、bar
がインスタンス変数ではなくクラス属性として定義されていることです。
foo
では、クラス属性がinit
メソッドで変更されているため、すべてのインスタンスが影響を受けます。
foo2
、インスタンス変数は(空の)クラス属性を使用して定義され、すべてのインスタンスは独自のbar
を取得します。
「正しい」実装は次のようになります。
class foo:
def __init__(self, x):
self.bar = [x]
もちろん、クラス属性は完全に合法です。実際、次のようにクラスのインスタンスを作成せずに、それらにアクセスして変更できます。
class foo:
bar = []
foo.bar = [x]
多くの時間が経過し、多くの正しいことが言われましたが、両方の効果をまとめる答えはありません。
次の2つの効果があります。
+=
_のリストの動作( Scott Griffiths で述べられているように)クラスfoo
では、___init__
_メソッドがクラス属性を変更します。 _self.bar += [x]
_がself.bar = self.bar.__iadd__([x])
に変換されるためです。 __iadd__()
はインプレース変更用であるため、リストを変更し、リストへの参照を返します。
インスタンスdictは変更されますが、クラスdictには既に同じ割り当てが含まれているため、通常は必要ありません。したがって、この詳細はほとんど気付かれません-後で_foo.bar = []
_を実行する場合を除きます。ここで、インスタンスのbar
は、上記の事実のおかげで同じままです。
ただし、クラス_foo2
_では、クラスのbar
が使用されますが、変更されません。代わりに、self.bar.__add__([x])
がここで呼び出され、オブジェクトを変更しないため、_[x]
_がそれに追加され、新しいオブジェクトを形成します。結果はインスタンス辞書に入れられ、クラスの属性は変更されたままで、インスタンスに辞書として新しいリストが与えられます。
_... = ... + ...
_と_... += ...
_の違いは、その後の割り当てにも影響します。
_f = foo(1) # adds 1 to the class's bar and assigns f.bar to this as well.
g = foo(2) # adds 2 to the class's bar and assigns g.bar to this as well.
# Here, foo.bar, f.bar and g.bar refer to the same object.
print f.bar # [1, 2]
print g.bar # [1, 2]
f.bar += [3] # adds 3 to this object
print f.bar # As these still refer to the same object,
print g.bar # the output is the same.
f.bar = f.bar + [4] # Construct a new list with the values of the old ones, 4 appended.
print f.bar # Print the new one
print g.bar # Print the old one.
f = foo2(1) # Here a new list is created on every call.
g = foo2(2)
print f.bar # So these all obly have one element.
print g.bar
_
print id(foo), id(f), id(g)
を使用してオブジェクトのIDを確認できます(Python3を使用している場合は、追加の_()
_ sを忘れないでください)。
ところで:_+=
_演算子は「拡張代入」と呼ばれ、通常は可能な限りインプレース変更を行うことを目的としています。
ここには2つのことが関係します。
1. class attributes and instance attributes
2. difference between the operators + and += for lists
+
演算子は、リストの__add__
メソッドを呼び出します。オペランドからすべての要素を取得し、それらの要素を含む新しいリストを作成して順序を維持します。
+=
演算子は、リストの__iadd__
メソッドを呼び出します。 iterableを取り、そのiterableのすべての要素をリストに追加します。新しいリストオブジェクトは作成されません。
クラスfoo
では、ステートメントself.bar += [x]
は割り当てステートメントではありませんが、実際には次のように変換されます
self.bar.__iadd__([x]) # modifies the class attribute
リストを所定の場所に変更し、リストメソッドextend
のように機能します。
クラスfoo2
では、逆に、init
メソッドの割り当てステートメント
self.bar = self.bar + [x]
次のように分解できます。
インスタンスには属性bar
がありませんが(同じ名前のクラス属性があります)、クラス属性bar
にアクセスし、x
を追加して新しいリストを作成します。ステートメントは次のように翻訳されます。
self.bar = self.bar.__add__([x]) # bar on the lhs is the class attribute
次に、インスタンス属性bar
を作成し、新しく作成したリストを割り当てます。割り当てのrhsのbar
は、lhsのbar
とは異なることに注意してください。
クラスfoo
のインスタンスの場合、bar
はクラス属性であり、インスタンス属性ではありません。したがって、クラス属性bar
への変更はすべてのインスタンスに反映されます。
それどころか、クラスfoo2
の各インスタンスには、同じ名前bar
のクラス属性とは異なる独自のインスタンス属性bar
があります。
f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]
print f.__class__.bar # accessing the class attribute. prints []
これが物事をクリアすることを願っています。
他の答えはそれをほとんどカバーしているように見えますが、 Augmented Assignments PEP 2 を引用して参照する価値があるようです。
それらは、[拡張代入演算子]は、通常のバイナリ形式と同じ演算子を実装します。ただし、左-左側のオブジェクトはそれをサポートし、左側は一度だけ評価されます。
...
Pythonの拡張代入の背後にある考え方は、左側のオペランドにバイナリ演算の結果を保存する一般的な方法を記述するのが簡単な方法ではなく、問題の左側のオペランドは、それ自体の変更されたコピーを作成するのではなく、「それ自体で」動作する必要があることを知っています。
>>> elements=[[1],[2],[3]]
>>> subset=[]
>>> subset+=elements[0:1]
>>> subset
[[1]]
>>> elements
[[1], [2], [3]]
>>> subset[0][0]='change'
>>> elements
[['change'], [2], [3]]
>>> a=[1,2,3,4]
>>> b=a
>>> a+=[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4, 5])
>>> a=[1,2,3,4]
>>> b=a
>>> a=a+[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4])