147

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 

出力

[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]

foo += barクラスのすべてのインスタンスに影響を与えるようですがfoo = foo + bar、私が期待するように動作するようです。

この+=演算子は「複合代入演算子」と呼ばれます。

4

9 に答える 9

169

一般的な答えは、特別なメソッド+=を呼び出そうとし、それが利用できない場合は代わりに使用しようとすることです。したがって、問題はこれらの特別な方法の違いにあります。__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は同等です。これは、不変の型で使用できるようにするもの+=です。そうしないと、数値などの不変の型で使用できないと考えるまでは、奇妙な設計上の決定に思えるかもしれません+=!

于 2010-02-27T13:14:11.973 に答える
101

一般的なケースについては、Scott Griffith の回答を参照してください。ただし、あなたのようなリストを扱う場合、+=演算子は の省略形ですsomeListObject.extend(iterableObject)extend()のドキュメントを参照してください。

このextend関数は、パラメーターのすべての要素をリストに追加します。

その場でfoo += somethingリストfooを変更する場合、名前fooが指す参照は変更しませんが、リスト オブジェクトを直接変更します。ではfoo = foo + something、実際に新しいリストを作成しています。

このサンプルコードはそれを説明します:

>>> l = []
>>> id(l)
13043192
>>> l += [3]
>>> id(l)
13043192
>>> l = l + [3]
>>> id(l)
13059216

新しいリストを に再割り当てすると、参照がどのように変化するかに注意してくださいl

barインスタンス変数ではなくクラス変数であるため、その場で変更すると、そのクラスのすべてのインスタンスに影響します。ただし、 を再定義するself.barと、インスタンスは別のインスタンス変数を持ちself.bar、他のクラス インスタンスには影響しません。

于 2010-02-27T12:19:23.760 に答える
22

ここでの問題はbar、インスタンス変数ではなくクラス属性として定義されていることです。

ではfoo、クラス アトリビュートがinitメソッド内で変更されるため、すべてのインスタンスが影響を受けます。

ではfoo2、インスタンス変数は (空の) クラス属性を使用して定義され、すべてのインスタンスが独自の を取得しますbar

「正しい」実装は次のようになります。

class foo:
    def __init__(self, x):
        self.bar = [x]

もちろん、クラス属性は完全に合法です。実際、次のようにクラスのインスタンスを作成せずに、それらにアクセスして変更できます。

class foo:
    bar = []

foo.bar = [x]
于 2010-02-27T12:23:17.963 に答える
9

ここでは、次の 2 つのことが関係しています。

1. class attributes and instance attributes
2. difference between the operators + and += for lists

+演算子は__add__、リストのメソッドを呼び出します。オペランドからすべての要素を取得し、それらの要素を順序を維持して含む新しいリストを作成します。

+=演算子は__iadd__、リストのメソッドを呼び出します。イテラブルを取り、イテラブルのすべての要素をリストに追加します。新しいリスト オブジェクトは作成されません。

クラス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を作成し、新しく作成したリストをそれに割り当てます。bar割り当ての右側は、左側とは異なることに注意してくださいbar

class のインスタンスの場合foobarはクラス属性であり、インスタンス属性ではありません。したがって、クラス属性への変更barはすべてのインスタンスに反映されます。

逆に、クラスの各インスタンスには、同じ名前のクラス属性とは異なるfoo2独自のインスタンス属性があります。barbar

f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]  
print f.__class__.bar # accessing the class attribute. prints []  

これで問題が解決することを願っています。

于 2013-10-25T12:22:43.127 に答える
5

多くの時間が経過し、多くの正しいことが言われましたが、両方の効果をバンドルする答えはありません。

2つの効果があります:

  1. 「特別な」、おそらく見過ごされているリストの動作+=Scott Griffithsによる)
  2. クラス属性とインスタンス属性が関係しているという事実(CanBerkBüderによると)

クラスfooでは、__init__メソッドはクラス属性を変更します。にself.bar += [x]変換されるためself.bar = self.bar.__iadd__([x])です。__iadd__()インプレース変更用であるため、リストを変更し、リストへの参照を返します。

インスタンスのdictが変更されていることに注意してください。ただし、クラスのdictにはすでに同じ割り当てが含まれているため、通常は必要ありません。foo.bar = []したがって、この詳細はほとんど気付かれません-後で行う場合を除きます。barここでは、前述の事実のおかげで、インスタンスは同じままです。

foo2ただし、クラスでは、クラスbarが使用されますが、変更されません。代わりに、a[x]が追加され、self.bar.__add__([x])ここで呼び出されるように、オブジェクトを変更しない新しいオブジェクトが形成されます。結果はインスタンスのdictに入れられ、クラスの属性が変更されたままの状態で、インスタンスに新しいリストがdictとして与えられます。

との区別は... = ... + ...... += ...その後の割り当てにも影響します。

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 

オブジェクトのIDをで確認できます( Python3を使用している場合はprint id(foo), id(f), id(g)、追加のを忘れないでください)。()

ところで:+=演算子は「拡張代入」と呼ばれ、通常、可能な限りインプレース変更を行うことを目的としています。

于 2012-11-11T12:56:02.980 に答える
1
>>> 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])
于 2012-03-13T07:08:09.423 に答える
1
>>> a = 89
>>> id(a)
4434330504
>>> a = 89 + 1
>>> print(a)
90
>>> id(a)
4430689552  # this is different from before!

>>> test = [1, 2, 3]
>>> id(test)
48638344L
>>> test2 = test
>>> id(test)
48638344L
>>> test2 += [4]
>>> id(test)
48638344L
>>> print(test, test2)  # [1, 2, 3, 4] [1, 2, 3, 4]```
([1, 2, 3, 4], [1, 2, 3, 4])
>>> id(test2)
48638344L # ID is different here

不変オブジェクト (この場合は整数) を変更しようとすると、Python は代わりに別のオブジェクトを提供するだけです。一方、変更可能なオブジェクト (リスト) に変更を加えて、全体を通して同じオブジェクトのままにすることができます。

参照: https://medium.com/@tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95

以下の URL も参照して、shallowcopy と deepcopy を理解してください。

https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/

于 2019-10-31T15:58:52.413 に答える