一个有趣的python问题
quiz
今天在twitter上看到一个有趣的问题。
t = (1, 2, [30, 40])
t[2] += [50, 60]
结果是什么?
- t = (1, 2, [30, 40, 50, 60])
- TypeError
- 都不是
- 1,2都发生
出乎意料的,结果是4。
为什么,我们来分析一下。
disassemble
>>> t = (1, 2, [30, 40])
>>> def f(): t[2] += [50, 60]
>>> import dis
>>> dis.dis(f)
以下是结果。
LOAD_GLOBAL 0 (t)
LOAD_CONST 1 (2)
DUP_TOPX 2
BINARY_SUBSCR
LOAD_CONST 2 (50)
LOAD_CONST 3 (60)
BUILD_LIST 2
INPLACE_ADD
ROT_THREE
STORE_SUBSCR
LOAD_CONST 0 (None)
RETURN_VALUE
我们来解读一下。dis的指令集可以看这里 。
- 先载入全局变量t(因为写法问题,t是全局)。
- 载入常数2。现在栈是t|2。
- 复制头两项。现在栈应当是t|2|t|2。
- BINARY_SUBSCR,取t[2]。现在是t|2|t[2]。
- 载入50和60,然后build_list。现在是t|2|t[2]|[50,60]。
- INPLACE_ADD,注意这里。现在是t|2|[30,40,50,60]。同时t[2]的list成为了[30,40,50,60]。
- ROT_THREE,结果是[30,40,50,60]|t|2。
- STORE_SUBSCR,实际是在操作t[2] = [30,40,50,60]。当然,这里出错了。
- 最后是return None,这步并未发生。
compare with add
整个最诡异的地方,就在于INPLACE_ADD。从字面上,这要求inplace的做加法。我们换个写法,然后再看看。
>>> def f(): t[2] = t[2] + [50, 60]
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in f
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40])
>>> dis.dis(f)
以下为结果。
LOAD_GLOBAL 0 (t)
LOAD_CONST 1 (2)
BINARY_SUBSCR
LOAD_CONST 2 (50)
LOAD_CONST 3 (60)
BUILD_LIST 2
BINARY_ADD
LOAD_GLOBAL 0 (t)
LOAD_CONST 1 (2)
STORE_SUBSCR
LOAD_CONST 0 (None)
RETURN_VALUE
看到没,结果就不一样了。
why
没有为什么,因为+=对应的指令就是INPLACE_ADD。
只是INPLACE_ADD了,理论上就不需要赋值了。我没想明白的是,在什么情况下INPLACE_ADD了又不赋值可能导致错误呢?(我觉得可能有这种情况)