有人告诉我它+=
可能会与标准符号产生不同的影响i = i +
。是否有与以下情况i += 1
不同的情况i = i + 1
?
i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]
是True
。许多开发人员可能不会注意到id(i)
对一项操作的更改,而对另一项操作则没有。
有人告诉我它+=
可能会与标准符号产生不同的影响i = i +
。是否有与以下情况i += 1
不同的情况i = i + 1
?
i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]
是True
。许多开发人员可能不会注意到id(i)
对一项操作的更改,而对另一项操作则没有。
Answers:
这完全取决于对象i
。
+=
调用__iadd__
方法(如果存在- __add__
如果不存在则返回),而+
调用__add__
方法1或在__radd__
某些情况下调用方法2。
从API的角度来看,__iadd__
应该将其用于就地修改可变对象(返回已变异的对象),而__add__
应该返回某些东西的新实例。对于不可变的对象,这两种方法都返回一个新实例,但是__iadd__
会将新实例放置在当前名称空间中,其名称与旧实例的名称相同。这就是为什么
i = 1
i += 1
似乎在增加i
。实际上,您将获得一个新的整数并将其分配给“顶部” i
-丢失对旧整数的一个引用。在这种情况下,i += 1
与完全相同i = i + 1
。但是,对于大多数易变的物体,情况就不同了:
作为一个具体的例子:
a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a #[1, 2, 3, 1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
相比:
a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
请注意,在第一个示例中,由于b
和a
引用相同的对象,因此当我+=
在on上使用时b
,它实际上发生了变化b
(并且也a
看到了这种变化-毕竟,它引用了相同的列表)。但是,在第二种情况下,当我这样做时b = b + [1, 2, 3]
,它将采用b
引用的列表并将其与新列表连接起来[1, 2, 3]
。然后,它将串联的列表存储为b
-而不考虑b
之前的行。
1在表达式中x + y
,如果x.__add__
未实现或如果x.__add__(y)
返回NotImplemented
并且 x
和y
具有不同的类型,则x + y
尝试调用y.__radd__(x)
。所以,如果你有
foo_instance += bar_instance
如果Foo
没有实现__add__
或__iadd__
那么这里的结果与
foo_instance = bar_instance.__radd__(bar_instance, foo_instance)
2在表达式中foo_instance + bar_instance
,如果的类型是(例如)类型的子类,bar_instance.__radd__
则将在之前尝试。合理的,这是因为在某种意义上是一种“更高级”的对象不是那么应该得到压倒一切的选项的行为。foo_instance.__add__
bar_instance
foo_instance
issubclass(Bar, Foo)
Bar
Foo
Bar
Foo
+=
调用,否则返回添加和重新绑定。这就是为什么即使没有也能正常工作的原因。但是除了那次小巧的话,还有很多解释。__iadd__
i = 1; i += 1
int.__iadd__
int.__iadd__
只是调用了__add__
。我很高兴今天学到了一些新东西:)。
x + y
呼叫y.__radd__(x)
x.__add__
NotImplemented
x
y
nb_inplace_add
或sq_inplace_concat
,并且这些C API函数比Python dunder方法具有更严格的要求,并且…但是我认为这与答案无关。主要区别在于,+=
尝试先进行就地添加,然后再回退为like +
,我想您已经解释了。
在幕后,i += 1
执行以下操作:
try:
i = i.__iadd__(1)
except AttributeError:
i = i.__add__(1)
虽然i = i + 1
做了这样的事情:
i = i.__add__(1)
这有点过分简化,但您会明白:Python +=
通过创建__iadd__
方法和,为类型提供了一种专门处理类型的方法__add__
。
目的是使可变类型(如list
)将自身变异__iadd__
(然后返回self
,除非您做的非常棘手),而可变类型(如int
将不会实现。
例如:
>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]
因为和l2
是同一对象l1
,并且您进行了突变l1
,所以您也进行了突变l2
。
但:
>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]
在这里,你没有变异l1
;相反,您创建了一个新列表,l1 + [3]
然后反弹该名称l1
以l2
指向该列表,而指向原始列表。
(在该+=
版本中,您还需要重新绑定l1
,只是在这种情况下,您需要将list
其重新绑定到已经绑定的相同的位置,因此通常可以忽略该部分。)
__iadd__
实际调用__add__
中的事件AttributeError
?
i.__iadd__
不打电话__add__
;就是i += 1
那个__add__
。
i = i.__iadd__(1)
- iadd
可以在适当位置修改对象,但不必这样做,因此在两种情况下都希望返回结果。
operator.iadd
电话__add__
上AttributeError
,但它不能重新绑定结果......所以i=1; operator.iadd(i, 1)
回报2和叶i
设置为1
。这有点令人困惑。
+=
就像extend()
列表一样。