自从接触了Python,就觉得Python的元组拆包(unpacking)非常有趣,非常简洁易用。最明显的例子就是多重赋值,即在一条语句中同时给多个变量赋值:>>>x,y=1,2>>>print(x,y)#Result:12中这个例子,赋值操作会将“=”号右边的两个数存入一个元组,变成(1,2),然后解包赋值给“=”号左边的两个变量="依次签到。如果我们直接写x=1,2然后打印x,或者在“=”号右边写一个元组,就可以证实这一点:>>>x=1,2>>>print(x)#Result:(1,2)>>>x,y=(1,2)>>>print(x,y)#Result:12一些博客或者公众号文章在介绍这个功能的时候,一般都会按照the来举个例子,基于两个变量,直接交换它们的值:>>>x,y=1,2>>>x,y=y,x>>>print(x,y)#result:21一般来说,操作交换两个变量需要引入第三个变量。道理很简单,想要调换两个杯子里的水,自然需要第三个容器作为中转。但是Python的写法不需要使用中间变量,其形式和前面的解包赋值是一样的。因为这种类似的形式,很多人误认为Python的变量交换操作也是基于拆包操作。但是,真的是这样吗?我搜索了一下,发现有人试图回答这个问题,但他们的回答一般都不够全面。(当然,错误的答案很多,更多的人只是知道而已,却没想过知道为什么)先放出本文的答案:Python的交换变量操作并不完全基于拆包操作,有时是,有时不是!你觉得这个答案很神奇吗?闻所未闻吗?!这是怎么回事?让我们看一下标题中最简单的两个变量。我们去dis杀看看编译后的字节码:上图打开两个窗口,可以很方便的比较“a,b=b,a”和“a,b=1,2”的区别:“a,b=b,a"操作:两个LOAD_FAST是从局部作用域中读取变量的引用并存入栈中,接着是最关键的ROT_TWO操作,会交换两个变量的引用值,以及然后两个STORE_FAST都是把变量存储在栈中,变量都写到局部作用域。“a,b=1,2”操作:第一步LOAD_CONST将“=”号右边的两个数作为元组入栈,第二步UNPACK_SEQUENCE为序列拆包,然后写入拆包结果进入本地范围内的变量。显然,这两种形式相似的写法实际上执行的是不同的操作。在交换变量的操作中,没有打包和解包的步骤!ROT_TWO指令是CPython解释器对栈顶的两个元素实现的快捷操作,改变它们指向的引用对象。类似的还有两个指令ROT_THREE和ROT_FOUR,分别是交换三变量和四变量的快捷方式(来自:ceval。ROT_FOUR是3.8版本新加入的:ROT_TWOSwapthetwotop-moststackitems.ROT_THREELiftssecondandthirdstackitemonepositionup,movestopdowntopositionthree.ROT_FOURLiftssecond,thirdandforthstackfourmsonepositionup.8.newmovestopdownversiontoposition3.CPython专用优化指令。就像[-5,256]这些小整数被预先放置在整数池中。对于更多变量的交换操作,实际上会用到上面提到的拆包操作:截图中的BUILD_TUPLE指令会将给定数量的栈顶元素创建成元组,然后被UNPACK_SEQUENCE指令拆包,依次赋值.值得一提的是,之所以比之前的“a,b=1,2”多了一个build操作,是因为每个变量的LOAD_FAST需要先单独入栈,不能直接组合成LOAD_CONST并推入堆栈。也就是说,当“=”号右边有变量的时候,就不会出现上一篇LOAD_CONST一个元组的情况了。最后,还有一个细节值得一提。这些指令与堆栈中元素的数量有关,与赋值语句中实际交换的变量数量无关。看个例子你就明白了:分析到这里,你应该明白前面的结论是怎么回事了吧?稍微总结一下:Python可以在一条语句中实现多次赋值,利用了序列拆包的特性。Python可以在一条语句中实现变量交换,不需要引入中间变量。当变量个数小于4(从3.8版本开始小于5)时,CPython使用ROT_*指令交换堆栈中的元素。当变量个数超过时,使用序列拆包的特性。序列拆包是Python的一大特色,但在本文的例子中,CPython解释器在小操作中也提供了几个优化的指令,这绝对会超出大多数人的认知
