偶然发现的坑,记录一下
示例代码
def func_a():
a = ['a']
b = a
b += ['b']
print(a,b,id(a)==id(b))
def func_b():
a = ['a']
b = a
b = b + ['b']
print(a,b,id(a)==id(b))
func_a() #运行结果为['a', 'b'] ['a', 'b'] True
func_b() #运行结果为['a'] ['a', 'b'] False
初步分析
我们知道b = b + [‘b’]这样的加法赋值运算,python解释器会先执行b + [‘b’]生成一个新的列表对象,然后将新的列表赋值给变量b。所以最初的分析是+=运算类似于append方法,是对列表对象就地修改。
深入分析
我们知道,加法运算是通过__add__方法实现的,而+=运算则是通过__iadd__方法实现。通过dis库查看字节码可以看出区别:
import dis
def func_a():
a = ['a']
b = a
b += ['b']
def func_b():
a = ['a']
b = a
b = b + ['b']
dis.dis(func_a)
dis.dis(func_b)
>>>
4 0 LOAD_CONST 1 ('a')
2 BUILD_LIST 1
4 STORE_FAST 0 (a)
5 6 LOAD_FAST 0 (a)
8 STORE_FAST 1 (b)
6 10 LOAD_FAST 1 (b)
12 LOAD_CONST 2 ('b')
14 BUILD_LIST 1
16 INPLACE_ADD #<====
18 STORE_FAST 1 (b)
20 LOAD_CONST 0 (None)
22 RETURN_VALUE
9 0 LOAD_CONST 1 ('a')
2 BUILD_LIST 1
4 STORE_FAST 0 (a)
10 6 LOAD_FAST 0 (a)
8 STORE_FAST 1 (b)
11 10 LOAD_FAST 1 (b)
12 LOAD_CONST 2 ('b')
14 BUILD_LIST 1
16 BINARY_ADD #<====
18 STORE_FAST 1 (b)
20 LOAD_CONST 0 (None)
22 RETURN_VALUE
发现b += [‘b’]和b = b + [‘b’]在标记的位置字节码是不同的,+=是就地添加,而+仅仅是二进制加法。
举一反三
与加法相同的,是通过__mul__实现,=是通过__imul__实现,其原理是一致的。
值得注意的是,不可变类型(int、str、tuple等)执行+=和*=时是会生成新的不同内存ID的对象的。
最后留个小问题,下面函数会报错吗?函数的执行结果是什么?
def func_a():
a = (1,2,[3,4])
b = a[2]
b += [5]
print(a)
def func_b():
a = (1,2,[3,4])
a[2].append(5)
print(a)
def func_c():
a = (1,2,[3,4])
try:
a[2] += [5]
except TypeError as e:
print(e)
print(a)
def func_d():
a = (1,2,[3,4])
try:
a[1] = 5
except TypeError as e:
print(e)
print(a)
func_a()
func_b()
func_c()
func_d()