序列+和*操作
示例:
l = [1,2,3]
print(id(l), l)
l *= 2 #运用增量乘法后,列表的 ID 没变,新元素追加到列表上
print(id(l), l)
l = l * 2 #此时创建一个新的对象后 赋值给了l
print(id(l), l)
#元组的是不可变序列
t = (1,2,3)
print(id(t), t)
t *= 2 #运用增量乘法后,新的元组被创建
print(id(t), t)
打印如下:
#序列操作
39414280 [1, 2, 3]
39414280 [1, 2, 3, 1, 2, 3]
39394056 [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
#元组操作
39235872 (1, 2, 3)
39303976 (1, 2, 3, 1, 2, 3)
关于+=
此部分内容引自:流畅的Python,2.6节
t = (1, 2, [3,4])
print(id(t), t)
try:
#此操作会抛出异常,因为元组是不可变序列
t[2] += [5,6] #TypeError: 'tuple' object does not support item assignment
except TypeError as e:
print(e)
#实际上打印显示,元组已经被修改
print(id(t), t)
#使用list的extend则不会报错
t[2].extend([7,8])
print(id(t), t)
打印如下:
39416384 (1, 2, [3, 4])
'tuple' object does not support item assignment
39416384 (1, 2, [3, 4, 5, 6]) #元组确实被修改了
39416384 (1, 2, [3, 4, 5, 6, 7, 8])
我们可以使用dis模块查看’t[2] += [5,6]’操作背后的字节码:
>>> dis.dis('t[2] += [5,6]')
1 0 LOAD_NAME 0 (t)
2 LOAD_CONST 0 (2)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_CONST 1 (5)
10 LOAD_CONST 2 (6)
12 BUILD_LIST 2
14 INPLACE_ADD
16 ROT_THREE
18 STORE_SUBSCR
20 LOAD_CONST 3 (None)
22 RETURN_VALUE
先将t[2]的值存入栈顶TOS,BUILD_LIST来构建一个带有变量5和6的列表,而INPLACE_ADD则表示,计算TOS += [5,6],这一步之所以可以成功,是因为TOS指向的是一个可变对象(列表)。然而最后t[2] = TOS赋值失败了,是因为t是不可变的元组!!!
除非这是我们想要的操作,否则尽量不要使用。流畅的Python的作者总结了3点教训,给大家分享下:
- 不要把可变对象放在元组里面。
- 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作。
- 查看 Python 的字节码并不难,而且它对我们了解代码背后的运行机制很有帮助。