当前位置: 首页 > 后端技术 > Python

Python中元组+=赋值的四道题

时间:2023-03-25 22:30:10 Python

最近偶尔看看《Fluent Python》,写下一些有趣的事情。下面是关于AugmentedAssignmentoftuple的问题,是增量赋值的问题,在PyCon2013上提出来的。并基于这个问题,衍生出三个变体问题。问题先看第一题,比如下面的代码段:>>>t=(1,2,[30,40])>>>t[2]+=[50,60]结果会是什么?给出了四个选项:1.`t`变为`[1,2,[30,40,50,60]`2.引发TypeError并显示消息“元组”对象不支持项目分配3.两者都不支持1nor24.1和2都可以根据之前的理解,元组中的元素是不能修改的,所以我会选择2。如果是这样的话,这个注释就没有必要了,《Fluent Python》不会取出一段.正确答案是4:>>>t=(1,2,[30,40])>>>t[2]+=[50,60]Traceback(最近调用最后):文件“”,line1,inTypeError:'tuple'objectdoesnotsupportitemassignment>>>t(1,2,[30,40,50,60])问题来了,为什么所有的异常都出来了,但现在还是变了?再来看第二种情况,把+=改成=:>>>t=(1,2,[30,40])>>>t[2]=[50,60]结果会变成酱:>>>t=(1,2,[30,40])>>>t[2]=[50,60]Traceback(最近调用最后):文件“”,第1行,在TypeError:'tuple'objectdoesnotsupportitemassignment>>>t(1,2,[30,40])看第三种情况,只需将+=改为extendorappend:>>>>t=(1,2,[30,40])>>>t[2]。扩展([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(最近调用最后):文件“”,第1行,在TypeError:'tuple'objectdoesnotsupportitemassignment>>>t(1,2,[30,40,50,60,70,80])是另一种情况,我们来探究一下原因原因是先回顾一下+=运算符,比如a+=b:对于列表等可变对象,+=运算的结果会直接在a对应的变量中修改,而a对应的地址不变.对于tuple这样的不可变对象(imutableobject),+=等价于a=a+b,会生成一个新的变量,然后绑定到a上。正如您在以下代码段中看到的:>>>a=[1,2,3]>>>id(a)53430752>>>a+=[4,5]>>>a[1,2,3,4,5]>>>id(a)53430752#addressnochange>>>b=(1,2,3)>>>id(b)49134888>>>b+=(4,5)>>>b(1,2,3,4,5)>>>>id(b)48560912#地址变了。另外需要注意的是,python中的元组是一个不可变对象,也就是我们平时说的元素不能改变。其实报错TypeError:'tuple'objectdoesnotsupport从项赋值的角度来说,更准确的说是里面的元素不支持赋值操作=(assignment)。我们先来看最简单的第二种情况,它的结果符合我们的预期,因为=产生了assign的操作。(在example到python的命名空间中,赋值操作=是新建一个变量),所以s[2]=[50,60]会抛出异常。再看第三种情况,包括extend/append,tuple中的list值发生了变化,但是没有抛出异常。这个其实比较容易理解。因为我们知道元素对应的地址(id)其实是存放在元组中的,所以如果没有赋值操作并且元组中元素的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目前已经解决了第二个和第三个问题,我们来排序先out,其实有两点:元组内部的元素不支持赋值操作。在第一项的基础上,如果元素的id不改变,实际上可以改变元素。现在来看第一个问题:t[2]+=[50,60]根据上面的结论,应该不会抛出异常,因为在我们看来+=是对变量对象t的就地操作[2],即直接修改自己的内容,id没有变,确认id没变:>>>a=(1,2,[30,40])>>>id(a[2])140628739587392>>>a[2]+=[50,60]Traceback(最近调用最后):文件“”,第1行,在TypeError中:“元组”对象不支持itemassignment>>>a(1,2,[30,40,50,60])>>>id(a[2])#ID没有变140628739587392第三题只是从t[2]开始变了。扩展到t[2]+=,抛出异常,所以问题应该在+=上。接下来使用dis模块查看它们执行的步骤,在下面的代码块上执行dis:t=(1,2,[30,40])t[2]+=[50,60]t[2].extend([70,80])执行python-mdistest.py,结果如下,只有第2、3行代码的执行过程,关键步骤的注释如下:221LOAD_NAME0(t)24LOAD_CONST1(2)27DUP_TOPX230BINARY_SUBSCR31LOAD_CONST4(50)34LOAD_CONST5(60)37BUILD_LIST240INPLACE_ADD41ROT_THREE42STORE_SUBSCR343LOAD_NAME0(t)46LOAD_CONST1(2)49BINARY_SUBSCR50LOAD_ATTR1(扩展)LOAD_CONST56(扩展)6LOAD_CONST56(t)(80)59BUILD_LIST262CALL_FUNCTION165POP_TOP66LOAD_CONST8(None)69RETURN_VALUE解释一下关键语句:30BINARY_SUBSCR:表示把t[2]的值放到TOS(TopofStack)中,这里指的是[30,40]Thislist40INPLACE_ADD:表示TOS+=[50,60]这一步执行可以成功,修改后的TOS列表为[30,40,50,60]42STORE_SUBSCR:表示s[2]=TOS问题就出在这里,这里产生了一个赋值操作,所以会抛出异常!不过上面对list的修改已经完成,这也解释了开头的第一个问题再看extend过程,和之前一样,只有这一行:62CALL_FUNCTION:这个是直接调用内置的extend函数完成对原list的修改,并没有assign操作,所以可以正常执行.现在渐渐清楚了。也就是说+=不是原子操作,相当于下面两步:t[2].extend([50,60])t[2]=t[2]第一步可以正确执行,但是在第二步加上=,肯定会抛出异常。同样,这也可以解释为什么使用+=时t[2]的id没有改变,但还是抛出异常。现在总结一句话:元组中的元素不支持assign操作,但是对于那些是可变对象的元素,比如列表、字典等,在没有assign操作的基础上,比如一些就地操作,内容可以修改可以用第四题简单验证一下,用一个指向[30,40]的名字a作为元素的值,然后就地修改a,不涉及tuple的assign操作,然后绝对可以正常工作。以上就是本次分享的全部内容。想了解更多python知识,请前往公众号:Python编程学习圈,发“J”免费领取,每日干货分享