记一次+=与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。但元组是不可变序列,所以赋值失败。

上一篇下一篇

猜你喜欢

热点阅读