Python 程序员肯定知道 a,b = b,a,这句话用来交换两个变量。相较于其它语言需要引入一个 temp 来临时存储变量的做法,Python 的这种写法无疑非常优雅。

佶屈聱牙的 C 写法:

int a = 1;
int b = 2;
int temp;
temp = a;
a = b;
b = temp;

简洁优雅的 Python 写法:

a,b = 1,2
a,b = b,a

虽然语法非常方便,但我们始终不曾想过:它是怎么运作的?背后支撑它的机制是什么?下面让我们一步步分析它。

通俗的说法

最常见的解释是:

a,b = b,a 中右侧是元组表达式,即 b,a 是一个两个元素的 tuple(a,b)。表达式左侧是两个待分配元素,而 = 相当于元组元素拆包赋值操作。

这种方法,理解起来最简单,但实际是这种情况么?

让我们从字节码上看下,是不是这种情况。

从字节码一窥交换变量

大家可能不太了解 Python 字节码。Python 解释器是一个基于栈的虚拟机。Python 解释器就是编译、解释 Python 代码的二进制程序。

虚拟机是一种执行代码的容器,相较于二进制代码具有方便移植的特点。而 Python 的虚拟机就是栈机器。

Python 中函数调用、变量赋值等操作,最后都转换为对栈的操作。这些对栈的具体操作,就保存在字节码里。

dis 模块可以反编译字节码,使其变成人类可读的栈机器指令。如下,我们看反编译 a,b=b,a 的代码。

>>> import dis
>>> dis.dis("a,b=b,a")1           0 LOAD_NAME                0 (b)2 LOAD_NAME                1 (a)4 ROT_TWO6 STORE_NAME               1 (a)8 STORE_NAME               0 (b)10 LOAD_CONST               0 (None)12 RETURN_VALUE

可见,在 Python 虚拟机的栈上,我们按照表达式右侧的 b,a 的顺序,先后压入计算栈中,然后用一个重要指令 ROT_TWO,这个操作交换了 ab 的位置,最后 STORE_NAME 操作将栈顶的两个元素先后弹出,传递给 ab 元素。

栈的特性是先进后出(FILO)。当我们按b,a顺序压入栈的时候,弹出时先出的就是a,再弹出就是b。STORE_NAME指令会把栈顶元素弹出,并关联到相应变量上。

如果没有第 4 列的指令 ROT_TWO,此次 STORE_NAME 弹出的第一个变量会是后压栈的 a,这样就是 a=a 的效果。有了 ROT_TWO 则完成了变量的交换。

好了,我们知道靠压栈、弹栈和交换栈顶的两个元素,实现了 a,b = b,a 的操作。

同时,我们也知道了,上诉元组拆包赋值的说法,是不恰当的。

ROT_TWO 是怎么具体操作的呢?

后台怎么执行?

见名知意,可以猜出来 ROT_TWO 是交换两个栈顶变量的操作。在 Python 源代码的层面上,来看是如何交换两个栈顶的元素。

下载 Python 源代码,进入 Python/ceval.c 文件,在 1101 行,我们看到了 ROT_TWO 的操作。

TARGET(ROT_TWO){PyObject *top = TOP();PyObject *second = SECOND();SET_TOP(second);SET_SECOND(top);FAST_DISPATCH();
}

代码比较简单,我们用 TOPSECOND 宏获取了栈上的 a,b 元素,然后再用 SET_TOP、SET_SECOND 宏把值写入栈中。以此完成交换栈顶元素的操作。

求值顺序的奇怪现象!

下面,我们来看一个奇怪的现象,在这篇文章里,也可以看到这个现象。如下,我们试图排序这个列表:

>>> a = [0, 1, 3, 2, 4]
>>> a[a[2]], a[2] = a[2], a[a[2]]
>>> a
>>> [0, 1, 2, 3, 4]
>>> a = [0, 1, 3, 2, 4]
>>> a[2], a[a[2]] = a[a[2]],a[2]
>>> a
>>> [0, 1, 3, 3, 4]

按照理解 a,b = b,a 和 b,a=a,b 是一样的结果,但从上例中我们看到,这两者的结果是不同的。

导致这一现象的原因在于:求值的顺序。毫无疑问,整个表达式先求右侧的两个元素,然后作为常数保存起来。最后赋值给左侧的两个变量。

最后赋值时,需要注意,我们从左到右依次赋值,如果 a[2] 先修改的话,势必会影响到其后的 a[a[2]] 的列表下标。

你可以使用反汇编代码,来分析产生这个现象的具体步骤。

奇怪的变回拆包现象!!

当我们使用常数作为右侧元组,来给左侧变量赋值时;或使用超过三个元素,来完成便捷交换时,其在字节码层次上便不是 ROT_TWO 这种操作了。

>>> dis.dis("a,b,c,d=b,c,d,a")1           0 LOAD_NAME3 LOAD_NAME6 LOAD_NAME9 LOAD_NAME12 BUILD_TUPLE15 UNPACK_SEQUENCE18 STORE_NAME21 STORE_NAME24 STORE_NAME27 STORE_NAME30 LOAD_CONST33 RETURN_VALUE
>>>

很明显,这里是在偏移 12 字节处 BUILD_TUPLE 组装元组,然后解包赋值给左侧变量。上文所述的通俗说法,在这里又成立了!

也就是说,当小于四个元素交换时,Python 采用优化的栈操作来完成交换。

当使用常量或者超过四个元素时,采用元组拆包赋值的方式来交换。

至于为什么是四个元素,应该是因为 Python 最多支持到 ROT_THREE 操作,四个元素的话,系统不知道该怎么优化了。但在新版本的 Python 中,我看到了 ROT_FOUR 操作,所以这时候,四个元素还是 ROT_* 操作来优化的。

>>>import opcode
>>>opcode.opmap["ROT_THREE"]
3

此例中,该版本 Python 支持 ROT_THREE 操作,你也可以使用 ROT_FOUR 查看自己 Python 是否支持,进而确定是否可以四个以上元素便捷交换。

总结

综上,我们了解了 Python 中优雅的 a,b = b,a 交换变量方法的实现和运行原理,深入了解其求值顺序和局限性,同时学习了深入分析 Python 代码的思路和方法,如果对你有帮助,可以给本文点个赞,也欢迎分享出去让更多人看见!

各位伙伴们好,詹帅本帅搭建了一个个人博客和小程序,汇集各种干货和资源,也方便大家阅读,感兴趣的小伙伴请移步小程序体验一下哦!(欢迎提建议)
推荐阅读牛逼!Python常用数据类型的基本操作(长文系列第①篇)
牛逼!Python的判断、循环和各种表达式(长文系列第②篇)牛逼!Python函数和文件操作(长文系列第③篇)牛逼!Python错误、异常和模块(长文系列第④篇)

抽丝剥茧,深入剖析 Python 如何实现变量交换!相关推荐

  1. 抽丝剥茧,深入剖析 Python 如何实现变量交换

    Python 程序员肯定知道 a,b = b,a,这句话用来交换两个变量.相较于其它语言需要引入一个 temp 来临时存储变量的做法,Python 的这种写法无疑非常优雅. 佶屈聱牙的 C 写法: i ...

  2. python学习-综合练习六(列表翻转、变量交换、复制列表、元素求和(lambda和递归)、字典排序、字典pop)

    文章目录 列表翻转 变量交换 复制列表 元素求和 字典排序 字典pop 说明:本篇博文的知识点大部分来自 Python3 实例 列表翻转 列表翻转就是把列表的值按顺序左移,逻辑还是挺简单的. 我之前的 ...

  3. Python基础语法-Python,Java,C++变量互换值的区别

    两个变量交换数值 C++ #include"iostream" using namespace std; int main(void){int a = 1;int b = 2;in ...

  4. Python基础-高级变量类型

    Python基础-高级变量类型 1.高级变量类型 """ 数值型:int float bool compex 非数值型:str list set dict tuple & ...

  5. Python 多变量赋值实现“交换”

    1. 两个变量交换   我们知道,Python 的对象有三要素:id(对象在内存中的地址).type(对象的数据类型).value(对象的值):而变量没有这三要素.   关于 "引用&quo ...

  6. python数字切片_剖析python切片「:」「::-1」「-1::」

    剖析python切片[:][::-1][-1::] 我们在面试python相关职位时,总是会碰到一些面试python切片的试题,另外在解决某些问题时也经常会用到切片操作,因此勇哥在这里给大家详细介绍p ...

  7. 【转载】 Python动态生成变量

    用Python循环创建多个变量, 如创建 a1=   .a2=   .a3=   .a4=   .a5=    或  self.a1=    .self.a2=   . self.a3= 一. 可以通 ...

  8. 硬核!Python 四种变量的代码对象和反汇编分析

    作者 | 大奎 整理 | 阳哥 来源丨Python数据之道 在Python基础的学习过程中,对变量和参数的理解有助于我们从更基础层面了解Python语言的运行.在这个过程中,还是有不少冷门和细节的地方 ...

  9. 深入解析Python中的变量和赋值运算符

    Python 变量类型 变量存储在内存中的值.这就意味着在创建变量时会在内存中开辟一个空间. 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中. 因此,变量可以指定不同的数据 ...

最新文章

  1. Mysql定期自动备份
  2. Python基础教程(十一):多线程、XML解析
  3. 使用System.Transactions
  4. Java面向对象(11)--多态性
  5. 小程序在父组件执行子组件方法,可适用于下拉刷新上拉加载之后执行子组件方法
  6. OO_BLOG3_规格化设计(JML学习)
  7. 三大开源生信基础教程(bookdown编写,源码可作为模板)和视频课程
  8. oracle tirger_又一次发现Oracle太美之awr相关脚本简介
  9. 草稿 断开式的连接 1127
  10. 山寨机来了:小米10至尊、透明版只要688
  11. python工程师工资多少-不止 20K,Python 工程师的薪资再度飙升!
  12. 吴恩达深度学习——卷积神经网络基础
  13. 大数据技术与应用专业 课 程 标 准
  14. 国企招聘:中国空空导弹2023校园招聘正式启动
  15. XYplorer使用教程
  16. 520,都来被虐吧。。。
  17. 花生日记涉传销,给了互联网企业什么启示
  18. Dubbo 实现原理与源码解析系列 —— 精品合集
  19. 逗号表达式与赋值语句
  20. 使用Oracle sql_trace 工具

热门文章

  1. Java 定时任务调度(8)--ElasticJob 入门实战(ElasticJob-Lite使用)
  2. 解决IDEA2020控制台乱码的方法(亲测)
  3. Python实现鸢尾花数据集分类问题——基于skearn的SVM(有详细注释的)
  4. Git的SourceTree添加授权添加用户名与密码
  5. Yii的action
  6. python int函数详解,python int()函数
  7. xposed框架在分机安装失败_免root用Xposed框架!安卓用户一定要学会
  8. rabbitmq延迟队列实现
  9. java8 遍历目录_使用java8API遍历过滤文件目录及子目录及隐藏文件
  10. ndows live id怎么登陆,手机如何注册和使用Windows Live ID帐号