记一次+=与extend
2021-07-05 本文已影响0人
倔犟的贝壳
看《流利的python》刷到的,记录一下
python代码如下:
#定义一个元组,元组的第2个元素是数组
a = (1,2,[3,4])
#定义一个数组
b = [5,6]
先把两个的内存地址打印出来
print("a的内存地址为:%s" %(id(a)))
print("a[2]的内存地址为:%s" %(id(a[2])))
a的内存地址为:140632039863680
a[2]的内存地址为:140632038209792
a[2] += b
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-35-1f2c16bcdd7a> in <module>
----> 1 a[2] += b
2 #print(id(a[2]+b))
TypeError: 'tuple' object does not support item assignment
此时会报错,元组对象不可赋值。但是打印a[2],a[2] 是已经被修改了的
print(a[2])
(1, 2, [3, 4, 5, 6])
但若是换成
a[2].extend(b)
print(a[2])
则不会报错,正常运行,输出a[2]
(1, 2, [3, 4, 5, 6])
为什么+=会报错,而extend不会报错呢?
我们先通过dis来查看一下a[2]+=b的字节码:
0 LOAD_GLOBAL 0 (a)
2 LOAD_CONST 1 (2)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_GLOBAL 1 (b)
10 INPLACE_ADD
12 ROT_THREE
14 STORE_SUBSCR
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
再查看extend的字节码:
0 LOAD_GLOBAL 0 (a)
2 LOAD_CONST 1 (2)
4 BINARY_SUBSCR
6 LOAD_ATTR 1 (extend)
8 LOAD_GLOBAL 2 (b)
10 BINARY_SUBSCR
12 POP_TOP
14 LOAD_CONST 0 (None)
16 RETURN_VALUE
通过字节码可以看到,+= 操作虽然使列表原地扩展了,但是当其保存的时候,a[2] = TOS的时候(STORE_SUBSCR),就会报错,元组对象不可赋值。而extend只是原地扩展序列,没有保存操作,所以不会报错。
+=背后的特殊方法是__iadd__("就地加法”,不会产生中间变量),如果一个类没有实现这个方法的话,python就会调用__add__方法(会产生中间变量)。对于可变序列,一般都实现了iadd_,而对于不可变序列,没有实现__iadd__。
元组不可变,而数组是可变的。所以当调用a[2]的时候,首先取出a[2]的元素是一个数组[3,4],存入TOS(栈的顶端),对这个数组进行+=操作,调用的__iadd__直接扩充数组为[3,4,5,6],然后需要把这个数组重新保存到元组,a[2] = TOS。但元组是不可变序列,所以赋值失败。