最近偶然翻看《Fluent Python》,遇到有意思的货色就记下来。上面的是在PyCon2013上提出的一个对于 tuple 的Augmented Assignment也就是增量赋值的一个问题。 并且基于此问题, 又引申出3个变种问题。
问题
首先看第一个问题, 如上面的代码段:
>>> t = (1,2, [30,40])>>> t[2] += [50,60]
会产生什么后果呢? 给出四个选项:
1. `t` 变成 `[1,2, [30,40,50,60]` 2. `TypeError is raised with the message 'tuple' object does not support item assignment` 3. Neither 1 nor 2 4. Both 1 and 2
依照之前的了解, tuple外面的元素是不能被批改的,因而会选 2.
如果真是这样的话,这篇笔记就没必要了,《Fluent Python》也就不会拿出一节来讲了。
正确答案是 4 :
>>> t = (1,2,[30,40])>>> t[2] += [50,60]Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: 'tuple' object does not support item assignment>>> t(1, 2, [30, 40, 50, 60])
问题来了,为什么异样都进去了,t 还是变了?
再看第二种状况,略微变动一下,将 += 变为 = :
>>> t = (1,2, [30,40])>>> t[2] = [50,60]
后果就成酱紫了:
>>> t = (1,2, [30,40])>>> t[2] = [50,60]Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: 'tuple' object does not support item assignment>>> t(1, 2, [30, 40])
再看第三种状况,只把+=换为extend或者append :
>>> t = (1, 2, [30,40])>>> t[2].extend([50,60])>>> t(1, 2, [30, 40, 50, 60])>>> t[2].append(70)>>> t(1, 2, [30, 40, 50, 60, 70])
又失常了,没抛出异样?
最初第四种状况,用变量的模式:
>>> a = [30,40]>>> t = (1, 2, a)>>> a+=[50,60]>>> a[30, 40, 50, 60]>>> t(1, 2, [30, 40, 50, 60])>>> t[2] += [70,80]Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: 'tuple' object does not support item assignment>>> t(1, 2, [30, 40, 50, 60, 70, 80])
又是一种状况,上面就探索一下其中的起因。
起因
首先须要重温+=这个运算符,如a+=b:
- 对于可变对象(mutable object)如list, +=操作的后果会间接在a对应的变量进行批改,而a对应的地址不变.
- 对于不可变对象(imutable object)如tuple, +=则是等价于a = a+b 会产生新的变量,而后绑定到a上而已.
如下代码段, 能够看进去:
>>> a = [1,2,3]>>> id(a)53430752>>> a+=[4,5]>>> a[1, 2, 3, 4, 5]>>> id(a)53430752 # 地址没有变动>>> b = (1,2,3)>>> id(b)49134888>>> b += (4,5)>>> b(1, 2, 3, 4, 5)>>> id(b)48560912 # 地址变动了
此外还须要留神的是, python中的tuple作为不可变对象, 也就是咱们平时说的元素不能扭转, 实际上从报错信息TypeError: 'tuple' object does not support item assignment来看, 更精确的说法是指其中的元素不反对赋值操作=(assignment).
先看最简略的第二种状况, 它的后果是合乎咱们的预期, 因为=产生了assign的操作.(在由一个例子到python的名字空间 中指出了赋值操作=就是创立新的变量), 因而s[2]=[50,60]就会抛出异样.
再看第三种状况,蕴含extend/append的, 后果tuple中的列表值产生了变动,然而没有异样抛出. 这个其实也绝对容易了解. 因为咱们晓得tuple中存储的其实是元素所对应的地址(id), 因而如果没有赋值操作且tuple中的元素的id不变,即可,而list.extend/append只是批改了列表的元素,而列表自身id并没有变动,看看上面的例子:
>>> a=(1,2,[30,40])>>> id(a[2])140628739513736>>> a[2].extend([50,60])>>> a(1, 2, [30, 40, 50, 60])>>> id(a[2])140628739513736
目前解决了第二个和第三个问题, 先梳理一下, 其实就是两点:
- tuple外部的元素不反对赋值操作
- 在第一条的根底上, 如果元素的id没有变动, 元素其实是能够扭转的.
当初再来看最后的第一个问题: t[2] += [50,60] 依照下面的论断, 不应该抛异样啊,因为在咱们看来+= 对于可变对象t[2]来说, 属于in-place操作,也就是间接批改本身的内容, id并不变, 确认下id并没有变动:
>>> a=(1,2,[30,40])>>> id(a[2])140628739587392>>> a[2]+=[50,60]Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: 'tuple' object does not support item assignment>>> a(1, 2, [30, 40, 50, 60])>>> id(a[2]) # ID 并没有产生扭转140628739587392
跟第三个问题仅仅从t[2].extend改成了t[2]+=, 就抛出异样了,所以问题应该是出在+=上了. 上面用dis模块看看它俩执行的步骤,对上面的代码块执行dis:
t = (1,2, [30,40])t[2] += [50,60]t[2].extend([70, 80])
执行python -m dis test.py,后果如下,上面只保留第2,3行代码的执行过程,以及关键步骤的正文如下:
2 21 LOAD_NAME 0 (t) 24 LOAD_CONST 1 (2) 27 DUP_TOPX 2 30 BINARY_SUBSCR 31 LOAD_CONST 4 (50) 34 LOAD_CONST 5 (60) 37 BUILD_LIST 2 40 INPLACE_ADD 41 ROT_THREE 42 STORE_SUBSCR3 43 LOAD_NAME 0 (t) 46 LOAD_CONST 1 (2) 49 BINARY_SUBSCR 50 LOAD_ATTR 1 (extend) 53 LOAD_CONST 6 (70) 56 LOAD_CONST 7 (80) 59 BUILD_LIST 2 62 CALL_FUNCTION 1 65 POP_TOP 66 LOAD_CONST 8 (None) 69 RETURN_VALUE
解释一下要害的语句:
- 30 BINARY_SUBSCR: 示意将t[2]的值放在TOS(Top of Stack),这里是指[30, 40]这个列表
- 40 INPLACE_ADD: 示意TOS += [50,60] 执行这一步是能够胜利的,批改了TOS的列表为[30,40,50,60]
- 42 STORE_SUBSCR: 示意s[2] = TOS 问题就出在这里了,这里产生了一个赋值操作,因而会抛异样!然而上述对列表的批改曾经实现, 这也就解释了开篇的第一个问题。
再看extend的过程,后面一样,只有这行:
- 62 CALL_FUNCTION: 这个间接调用内置extend函数实现了对原列表的批改,其中并没有assign操作,因而能够失常执行。
当初逐步清晰了, 换句话说,+=并不是原子操作,相当于上面的两步:
t[2].extend([50,60])t[2] = t[2]
第一步能够正确执行,然而第二步有了=,必定会抛异样的。 同样这也能够解释在应用+=的时候,为何t[2]的id明明没有变动,然而依然抛出异样了。
当初用一句话总结下:
tuple中元素不反对assign操作,然而对于那些是可变对象的元素如列表,字典等,在没有assign操作的根底上,比方一些in-place操作,是能够批改内容的
能够用第四个问题来简略验证一下,应用一个指向[30,40]的名称a来作为元素的值,而后对a做in-place的批改,其中并没有波及到对tuple的assign操作,那必定是失常执行的。
以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈 ,发送 “J” 即可收费获取,每日干货分享