问题:为什么+ =在列表上表现异常?
+=
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
似乎以我希望事情表现的方式表现。
该+=
运算符称为“化合物赋值运算符”。
The +=
operator in python seems to be operating unexpectedly on lists. Can anyone tell me what is going on here?
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
seems to affect every instance of the class, whereas foo = foo + bar
seems to behave in the way I would expect things to behave.
The +=
operator is called a “compound assignment operator”.
回答 0
一般的答案是+=
尝试调用__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
它们是等效的。这就是让您+=
在不可变类型上使用的原因,这似乎是一个奇怪的设计决定,除非您考虑到否则无法+=
在不可变类型(例如数字)上使用!
The general answer is that +=
tries to call the __iadd__
special method, and if that isn’t available it tries to use __add__
instead. So the issue is with the difference between these special methods.
The __iadd__
special method is for an in-place addition, that is it mutates the object that it acts on. The __add__
special method returns a new object and is also used for the standard +
operator.
So when the +=
operator is used on an object which has an __iadd__
defined the object is modified in place. Otherwise it will instead try to use the plain __add__
and return a new object.
That is why for mutable types like lists +=
changes the object’s value, whereas for immutable types like tuples, strings and integers a new object is returned instead (a += b
becomes equivalent to a = a + b
).
For types that support both __iadd__
and __add__
you therefore have to be careful which one you use. a += b
will call __iadd__
and mutate a
, whereas a = a + b
will create a new object and assign it to a
. They are not the same operation!
>>> 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
For immutable types (where you don’t have an __iadd__
) a += b
and a = a + b
are equivalent. This is what lets you use +=
on immutable types, which might seem a strange design decision until you consider that otherwise you couldn’t use +=
on immutable types like numbers!
回答 1
对于一般情况,请参见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
而不会影响其他类实例。
For the general case, see Scott Griffith’s answer. When dealing with lists like you are, though, the +=
operator is a shorthand for someListObject.extend(iterableObject)
. See the documentation of extend().
The extend
function will append all elements of the parameter to the list.
When doing foo += something
you’re modifying the list foo
in place, thus you don’t change the reference that the name foo
points to, but you’re changing the list object directly. With foo = foo + something
, you’re actually creating a new list.
This example code will explain it:
>>> l = []
>>> id(l)
13043192
>>> l += [3]
>>> id(l)
13043192
>>> l = l + [3]
>>> id(l)
13059216
Note how the reference changes when you reassign the new list to l
.
As bar
is a class variable instead of an instance variable, modifying in place will affect all instances of that class. But when redefining self.bar
, the instance will have a separate instance variable self.bar
without affecting the other class instances.
回答 2
这里的问题是,bar
被定义为类属性,而不是实例变量。
在中foo
,在init
方法中修改了class属性,这就是所有实例都受影响的原因。
在中foo2
,使用(空)class属性定义了一个实例变量,并且每个实例都有自己的bar
。
“正确”的实现将是:
class foo:
def __init__(self, x):
self.bar = [x]
当然,类属性是完全合法的。实际上,无需创建此类的实例即可访问和修改它们:
class foo:
bar = []
foo.bar = [x]
The problem here is, bar
is defined as a class attribute, not an instance variable.
In foo
, the class attribute is modified in the init
method, that’s why all instances are affected.
In foo2
, an instance variable is defined using the (empty) class attribute, and every instance gets its own bar
.
The “correct” implementation would be:
class foo:
def __init__(self, x):
self.bar = [x]
Of course, class attributes are completely legal. In fact, you can access and modify them without creating an instance of the class like this:
class foo:
bar = []
foo.bar = [x]
回答 3
这里涉及两件事:
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
它修改了列表并像list方法一样起作用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
分配的rhs bar
与lhs的不同。
对于class的实例foo
,bar
是class属性,而不是instance属性。因此,对class属性的任何更改bar
都将反映在所有实例中。
相反,该类的每个实例foo2
都有其自己的instance属性bar
,该属性不同于同名的class属性bar
。
f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]
print f.__class__.bar # accessing the class attribute. prints []
希望这能清除一切。
There are two things involved here:
1. class attributes and instance attributes
2. difference between the operators + and += for lists
+
operator calls the __add__
method on a list. It takes all the elements from its operands and makes a new list containing those elements maintaining their order.
+=
operator calls __iadd__
method on the list. It takes an iterable and appends all the elements of the iterable to the list in place. It does not create a new list object.
In class foo
the statement self.bar += [x]
is not an assignment statement but actually translates to
self.bar.__iadd__([x]) # modifies the class attribute
which modifies the list in place and acts like the list method extend
.
In class foo2
, on the contrary, the assignment statement in the init
method
self.bar = self.bar + [x]
can be deconstructed as:
The instance has no attribute bar
(there is a class attribute of the same name, though) so it accesses the class attribute bar
and creates a new list by appending x
to it. The statement translates to:
self.bar = self.bar.__add__([x]) # bar on the lhs is the class attribute
Then it creates an instance attribute bar
and assigns the newly created list to it. Note that bar
on the rhs of the assignment is different from the bar
on the lhs.
For instances of class foo
, bar
is a class attribute and not instance attribute. Hence any change to the class attribute bar
will be reflected for all instances.
On the contrary, each instance of the class foo2
has its own instance attribute bar
which is different from the class attribute of the same name bar
.
f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]
print f.__class__.bar # accessing the class attribute. prints []
Hope this clears things.
回答 4
尽管已经过去了很多时间,并且说了许多正确的话,但是还没有答案将这两种效果捆绑在一起。
您有2种效果:
- 列表的一种“特殊”的,也许未被注意的行为
+=
(如Scott Griffiths所述)
- 包含类属性和实例属性的事实(如Can BerkBüder所述)
在class中foo
,该__init__
方法修改了class属性。这是因为self.bar += [x]
翻译成self.bar = self.bar.__iadd__([x])
。__iadd__()
是用于就地修改的,因此它将修改列表并返回对其的引用。
请注意,实例字典已被修改,尽管通常不需要,因为类字典已包含相同的赋值。因此,这个细节几乎没有引起注意-除非您foo.bar = []
事后做。bar
由于上述事实,实例在这里保持不变。
foo2
但是,在class 中,使用了class bar
,但没有涉及。而是[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
您可以使用验证对象的身份print id(foo), id(f), id(g)
(()
如果您使用的是Python3,请不要忘记其他的)。
顺便说一句:+=
运算符被称为“扩充分配”,通常旨在尽可能进行就地修改。
Although much time has passed and many correct things were said, there is no answer which bundles both effects.
You have 2 effects:
- a “special”, maybe unnoticed behaviour of lists with
+=
(as stated by Scott Griffiths)
- the fact that class attributes as well as instance attributes are involved (as stated by Can Berk Büder)
In class foo
, the __init__
method modifies the class attribute. It is because self.bar += [x]
translates to self.bar = self.bar.__iadd__([x])
. __iadd__()
is for inplace modification, so it modifies the list and returns a reference to it.
Note that the instance dict is modified although this would normally not be necessary as the class dict already contains the same assignment. So this detail goes almost unnoticed – except if you do a foo.bar = []
afterwards. Here the instances’s bar
stays the same thanks to the said fact.
In class foo2
, however, the class’s bar
is used, but not touched. Instead, a [x]
is added to it, forming a new object, as self.bar.__add__([x])
is called here, which doesn’t modify the object. The result is put into the instance dict then, giving the instance the new list as a dict, while the class’s attribute stays modified.
The distinction between ... = ... + ...
and ... += ...
affects as well the assignments afterwards:
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
You can verify the identity of the objects with print id(foo), id(f), id(g)
(don’t forget the additional ()
s if you are on Python3).
BTW: The +=
operator is called “augmented assignment” and generally is intended to do inplace modifications as far as possible.
回答 5
其他答案似乎几乎涵盖了所有内容,尽管它似乎值得引用和参考增值作业PEP 203:
他们(增强的赋值运算符)实现了与普通二进制形式相同的运算符,不同之处在于,当左侧对象支持该操作时“就地”执行该操作,并且左侧仅被评估一次。
…
Python中的增强赋值背后的想法是,它不仅是编写将二进制运算的结果存储在其左操作数中的通用实践的简便方法,而且还是一种用于所讨论的左操作数的方法。知道它应该“自己”运行,而不是创建自己的修改副本。
The other answers would seem to pretty much have it covered, though it seems worth quoting and referring to the Augmented Assignments PEP 203:
They [the augmented assignment operators] implement the same operator
as their normal binary form, except that the operation is done
`in-place’ when the left-hand side object supports it, and that the
left-hand side is only evaluated once.
…
The idea behind augmented
assignment in Python is that it isn’t just an easier way to write the
common practice of storing the result of a binary operation in its
left-hand operand, but also a way for the left-hand operand in
question to know that it should operate `on itself’, rather than
creating a modified copy of itself.
回答 6
>>> 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])
>>> 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])
回答 7
>>> 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
另请参阅以下网址以了解浅拷贝和深拷贝
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
>>> 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
We see that when we attempt to modify an immutable object (integer in this case), Python simply gives us a different object instead. On the other hand, we are able to make changes to an mutable object (a list) and have it remain the same object throughout.
ref : https://medium.com/@tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95
Also refer below url to understand the shallowcopy and deepcopy
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/