Python 为什么+;=在名单上表现出人意料?
python中的Python 为什么+;=在名单上表现出人意料?,python,augmented-assignment,Python,Augmented Assignment,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
+=
运算符似乎在列表上意外运行。有人能告诉我这里发生了什么事吗
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
似乎会以我预期的方式运行
+=
运算符称为“复合赋值运算符” 有关一般情况,请参见。但是,在处理像您这样的列表时,+=
操作符是someListObject.extend(iterableObject)
的缩写。看
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
,而不会影响其他类实例。这里的问题是,bar
被定义为类属性,而不是实例变量
在foo
中,class属性在init
方法中被修改,这就是所有实例都受到影响的原因
在foo2
中,使用(空)class属性定义实例变量,每个实例都有自己的bar
f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]
print f.__class__.bar # accessing the class attribute. prints []
“正确”的实施将是:
class foo:
def __init__(self, x):
self.bar = [x]
当然,类属性是完全合法的。实际上,您可以访问和修改它们,而无需创建类的实例,如下所示:
class foo:
bar = []
foo.bar = [x]
一般的答案是,
+=
尝试调用\uuuuidd\uuuuu
特殊方法,如果不可用,则尝试使用\uuuuuu添加
。所以问题在于这些特殊方法之间的区别
\uuuu iadd\uuuu
特殊方法用于就地添加,即它对其作用的对象进行变异。\uuuuu添加
特殊方法返回一个新对象,也用于标准的+
运算符
因此,当+=
操作符用于定义了\uuu\uu\idd\uuu
的对象时,该对象将被就地修改。否则,它将尝试使用普通的\uuuuu添加\uuuuu
并返回一个新对象
这就是为什么对于列表之类的可变类型,+=
会更改对象的值,而对于元组、字符串和整数之类的不可变类型,则会返回一个新对象(a+=b
相当于a=a+b
)
对于同时支持\uuuu iadd\uuuu
和\uuuu add\uuuuu
的类型,因此您必须小心使用哪一种a+=b
将调用\uu iadd\uud
并变异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
对于不可变类型(您没有
\uuu iadd\uuu
)a+=b
和a=a+b
是等效的。这是让你在不可变类型上使用<代码> += ,这似乎是一个奇怪的设计决定,直到你考虑到,否则你不能在不可变类型的数字上使用<代码> += < /代码>!p> 虽然已经过去了很多时间,说了很多正确的话,但没有一个答案能将这两种效果结合起来
>>> 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])
您有两种效果:
+=
(如所述)的列表的“特殊”,可能未被注意到的行为foo
中,方法修改class属性。这是因为self.bar+=[x]
转换为self.bar=self.bar.\uuuuu iadd\uuuu([x])
\uu iadd\uu()
用于就地修改,因此它修改列表并返回对列表的引用
请注意,实例dict已被修改,尽管这通常不是必需的,因为类dict已经包含相同的赋值。因此,这个细节几乎没有被注意到——除非您在之后执行foo.bar=[]
。由于上述事实,这里实例的条保持不变
但是,在类foo2
中,使用了类的条
,但未触摸。相反,一个[x]
被添加到它,形成一个新的对象,如self.bar.。\uuuu 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(foo)、id(f)、id(g)
验证对象的身份(如果您使用的是Python3,请不要忘记附加的()
)
顺便说一句:+=
操作符被称为“增广赋值”,通常是为了尽可能地进行就地修改。其他答案似乎几乎涵盖了它,尽管它似乎值得引用和参考:
它们[增广赋值运算符]实现相同的运算符
作为它们的标准二进制形式,除了操作已完成
`当左侧对象支撑它时,且
左侧仅计算一次
增广后的想法
Python中的任务是,它不仅仅是一个更简单的任务
self.bar.__iadd__([x]) # modifies the class attribute
self.bar = self.bar + [x]
self.bar = self.bar.__add__([x]) # bar on the lhs is the class attribute
f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]
print f.__class__.bar # accessing the class attribute. prints []
>>> 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