今天wayne老师讲函数函数默认的作用域的时候,讲到列表+= 和 +的区别,一脸懵逼,自己遇到了知识盲区。
在这里总结一下+= 和 +的区别
一丶直入主题,先看示例
- 对象是列表的情况
In [1]: a = [1, 2, 3]
In [2]: a += (4,)
In [3]: a
Out[3]: [1, 2, 3, 4]
In [4]: b = [1, 2, 3]
In [5]: b = b + (4,)
Traceback (most recent call last):
File "<ipython-input-5-f5a90945a99d>", line 1, in <module>
b = b + (4,)
TypeError: can only concatenate list (not "tuple") to list
上面不难看出,a += b和a = a + b并不是完全等价的。
但是list和tuple相加操作,理应报错,为什么使用了+=却没有报错呢?
二丶深入理解
- 实际上,+= 会首先调用__iadd__方法,如果没有__iadd__,则调用 __add__方法,但是 + 只会调用 __add__这个方法。
- 对于不可变对象:元组丶字符串、数字、浮点数、bytes等这种对象,他们没有__iadd__对象,所以对他们来说,+= 和 + 是等价的。
对于__iadd__这个方法,当对象是列表时,
def __iadd__(self, values):
self.extend(values)
return self
官方文档: 类似__iadd__的这些方法尝试进行自身操作(修改self),并返回结果(结果应该但非必须为self)。如果某个方法未被定义,则被退回到普通方法.例如x是具有_iadd_方法的类的一个实例,则x += y 就相当于x = x.__iadd__(y).否则就如x + y 的求值一样选择x.__add__(y)或者y.__radd__(x)
Why does a_tuple[i] += [‘item’] raise an exception when the addition works?
所以说,当使用 += 连接列表和元组的时候,本质上是列表使用extend将元组的内容添加进去,所以a的身份地址id也不会变。
下面来验证通过+=和+操作前后的id内存地址的情况
- 可变对象
选列表作为操作对象
In [6]: l1 = [1, 2, 3]
In [7]: l2 = [1, 2, 3]
In [8]: id(l1), id(l2)
Out[8]: (162363016, 162714056)
In [9]: l1 += [4]
In [10]: l2 = l2 + [4]
In [11]: id(l1), id(l2)
Out[11]: (162363016, 138938120)
不难发现,列表经过+=操作后,id内存地址并没有发生改变,但是+却改变了。
2. 不可变对象
选字符串作为操作对象
In [1]: s1 = "a"
In [2]: s2 = "b"
In [3]: id(s1),id(s2)
Out[3]: (6327520, 6325840)
In [4]: s1 += "c"
In [5]: s2 = s2 + "c"
In [6]: id(s1), id(s2)
Out[6]: (168605264, 168612504)
不可变对象没有__iadd__方法,所以经过 += 和 + 之后,两者的id内存地址都发生了改变
引用廖雪峰python3教程中函数参数中的小节部分:
默认参数一定要用不可变对象,如果是可变对象,程序运行时会出现逻辑错误!
三丶知识点总结
-
简单的赋值不创建副本
-
对 += 或者 *=所做的增量赋值来说,如果左边的变量绑定的是不可变对象,会创建新对象:如果是可变对象,会就地修改
-
函数的参数以别名的形式传递,这意味着,函数可能会修改通过参数传入的可变对象。这一行为无法避免,除非在本地创建副本或者使用不可变对象
-
如果默认值是可变容器的话,比如说列表、集合、字典,那么应该把None作为默认值,代码应该像下面编写
def spam(a, b = None):
if b is None:
b = []
*理解有不恰当的地方请多多指教