社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python 为什么只需一条语句“a,b=b,a”,就能直接交换两个变量?

小白学视觉 • 3 年前 • 196 次点击  

点击上方小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

从接触 Python 时起,我就觉得 Python 的元组解包(unpacking)挺有意思,非常简洁好用。

最显而易见的例子就是多重赋值,即在一条语句中同时给多个变量赋值:

>>> x, y = 12
>>> print(x, y)  # 结果:1 2

在此例中,赋值操作符“=”号的右侧的两个数字会被存入到一个元组中,即变成 (1,2),然后再被解包,依次赋值给“=”号左侧的两个变量。

如果我们直接写x = 1,2 ,然后打印出 x,或者在“=”号右侧写成一个元组,就能证实到这一点:

>>> x = 12
>>> print(x)     # 结果:(1, 2)
>>> x, y = (12)
>>> print(x, y)  # 结果:1 2

一些博客或公众号文章在介绍到这个特性时,通常会顺着举一个例子,即基于两个变量,直接交换它们的值:

>>> x, y = 12
>>> x, y = y, x
>>> print(x, y) # 结果:2 1

一般而言,交换两个变量的操作需要引入第三个变量。道理很简单,如果要交换两个杯子中所装的水,自然会需要第三个容器作为中转。

然而,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.c 文件,最新的 3.9 分支):

预定义的栈顶操作如下:

查看官方文档中对于这几个指令的解释,其中 ROT_FOUR 是 3.8 版本新加的:

ROT_TWO

Swaps the two top-most stack items.


ROT_THREE

Lifts second and third stack item one position up, moves top down to position three.


ROT_FOUR

Lifts second, third and forth stack items one position up, moves top down to position four.
New in version 3.8.

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 解释器在小小的操作中还提供了几个优化的指令,这绝对会超出大多数人的认知

下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台回复:扩展模块中文教程即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

下载2:Python视觉实战项目52讲
小白学视觉公众号后台回复:Python视觉实战项目即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

下载3:OpenCV实战项目20讲
小白学视觉公众号后台回复:OpenCV实战项目20讲即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

交流群


欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/134296
 
196 次点击