Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/296.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 为什么+;=在名单上表现出人意料?_Python_Augmented Assignment - Fatal编程技术网

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

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
似乎会以我预期的方式运行

+=
运算符称为“复合赋值运算符”

有关一般情况,请参见。但是,在处理像您这样的列表时,
+=
操作符是
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])
您有两种效果:

  • 带有
    +=
    (如所述)的列表的“特殊”,可能未被注意到的行为
  • 涉及类属性和实例属性的事实(如所述)
  • 在class
    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