Python 为什么只需一条语句“a,b=b,a”,就能直接交换两个变量?
从接触 Python 时起,我就觉得 Python 的元组解包(unpacking)挺有意思,非常简洁好用。
最显而易见的例子就是多重赋值,即在一条语句中同时给多个变量赋值:
>>> x, y = 1, 2
>>> print(x, y) # 结果:1 2
在此例中,赋值操作符“=”号的右侧的两个数字会被存入到一个元组中,即变成 (1,2),然后再被解包,依次赋值给“=”号左侧的两个变量。
如果我们直接写x = 1,2
,然后打印出 x,或者在“=”号右侧写成一个元组,就能证实到这一点:
>>> x = 1, 2
>>> print(x) # 结果:(1, 2)
>>> x, y = (1, 2)
>>> print(x, y) # 结果:1 2
一些博客或公众号文章在介绍到这个特性时,通常会顺着举一个例子,即基于两个变量,直接交换它们的值:
>>> x, y = 1, 2
>>> 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 解释器在小小的操作中还提供了几个优化的指令,这绝对会超出大多数人的认知
如果你觉得本文分析得不错,那你应该会喜欢这些文章:
4、Python 为什么没有 main 函数?为什么我不推荐写 main 函数?
6、Python 为什么不支持 i++ 自增语法,不提供 ++ 操作符?
写在最后:本文属于“Python为什么”系列(Python猫出品),该系列主要关注 Python 的语法、设计和发展等话题,以一个个“为什么”式的问题为切入点,试着展现 Python 的迷人魅力。部分话题会推出视频版,请在 B 站收看,观看地址:视频地址
公众号【Python猫】, 本号连载优质的系列文章,有Python为什么系列、喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。
Python 为什么只需一条语句“a,b=b,a”,就能直接交换两个变量?的更多相关文章
- python不使用第三方变量,交换两个变量的值
#不使用第三个变量交换两个变量的值 a=1 b=2 a,b=b,a#python的直接交换 #另一种交换方法 a=a+b#a=3 b=2 b=a-b#a=3 b=1 a=a-b#a=2 b=1 pri ...
- python中如何调用函数交换两个变量的值
python中如何调用函数交换两个变量的值 所有代码来在python3.7.1版本实现 以下实例通过用户输入两个变量,并相互交换: 方法一: def swap(a,b): # 创建临时变量,并交换 ...
- python学习笔记(八)函数return多个值,列表推导式和交换两个变量的值
函数return多个值: 补充知识点:写代码得到时候少用全局变量: 1.不安全 2.会一直占着内存不释放 函数多个return值,那么会把这几个return的值都放在一个元组里面,然后返回 def h ...
- 上线 Python 应用仅需一条命令的开源框架:Zappa(详细教程)
本文面向有 Python Web 基础的小伙伴 作者:HelloGitHub-吱吱 这里是 HelloGitHub 推出的<讲解开源项目>系列,今天要向小伙伴们介绍一个 Python 无服 ...
- Python中的print、input函数以及Python中交换两个变量解析
一.Python中的值交换操作 首先明确一点点,Python中的一切都是面向对象的,可以理解为Python的中一切都是对象. 我们知道Java也是面向对象的语言,但是在Java中定义一个值变量如下: ...
- 【Python入门只需20分钟】从安装到数据抓取、存储原来这么简单
基于大众对Python的大肆吹捧和赞赏,作为一名Java从业人员,我本着批判与好奇的心态买了本python方面的书<毫无障碍学Python>.仅仅看了书前面一小部分的我......决定做一 ...
- mysql,Jdbc工具类,只需一条sql实现简单查询
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import ...
- python交换两个变量的值,一句代码搞定
a = 10 b = 20 # 不需要中间变量,一步搞定 a, b = b, a
- 逻辑推理:在一个100条语句的列表中,第n条语句是“在这个列表中,恰有n条语句为假”,可以得出什么结论?
<离散数学及其应用>第六版1.1练习题第43题的个人分析 题目:在一个100条语句的列表中,第n条语句是"在这个列表中,恰有n条语句为假".......... ...
随机推荐
- 最新 iOS 框架整体梳理(三)
这一篇得把介绍框架这个系列终结了,不能超过三篇了,不然太长了..... 还是老规矩,前面两篇的机票在下方: 最新 iOS 框架整体梳理(一) 最新 iOS 框架整体梳理(二) Part - 3 ...
- program files (x86)\microsoft visual studio 14.0\vc\include\xtree,如果没有找到,下标溢出了,就报错咯
---------------------------Microsoft Visual C++ Runtime Library---------------------------Debug Asse ...
- Task.Result跟 Task.GetAwaiter.GetResult()相同吗?怎么选?
前几天在用线程池执行一些任务时运到一种情形,就是回调方法中使用到了异步方法,但是回调方法貌似不支持async await的写法.这时候我应该如何处理呢?是使用Task.Result来获取返回结果,还是 ...
- Mac OS 生成 icon 和 ico 文件
[本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 1. 生成 IC ...
- 《Java核心技术》笔记:第7章 异常、断言和日志
1. 异常 (P 280)异常处理需要考虑的问题: 用户输入错误 设备错误 物理限制 代码错误 (P 280)传统的处理错误的方法是:返回一个特殊的错误码,常见的是返回-1或者null引用 (P 28 ...
- 第十章:RDB持久化
RDB 保存命令 save 命令,阻塞 Redis 服务器进程,直到保存动作完成: bgsave 命令,派生出一个子进程来完成保存动作: 载入命令 Redis 没有载入 RDB 文件的命令,载入动作在 ...
- PHPstudy 2018 集成环境项目配置虚拟域名访问
1.首先启动PHPstudy2018,并停止它 2.点击“其他选项菜单->站点域名管理” 3.设置站点域名.项目目录,点击“新增”,再点击“保存并生成配置文件” 4.生产配置文件之后会重启,然后 ...
- egret canvas的style
<canvas width="1920" height="1080" style=" cursor:auto;//鼠标样式 positon:ob ...
- git和github入门指南(6)
6.交作业的流程 以下内容是螺钉课堂在线就业班提交作业的要求,非螺钉课堂在线就业班学员不用学习 螺钉课堂作业全程采用git管理,希望在日常使用中,加深对git和github的理解 具体流程: 1.注册 ...
- 服务消费者(Ribbon)
上一篇文章,简单概述了服务注册与发现,在微服务架构中,业务都会被拆分成一个独立的服务,服务之间的通讯是基于http restful的,Ribbon可以很好地控制HTTP和TCP客户端的行为,Sprin ...