抽丝剥茧,深入剖析 Python 如何实现变量交换
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_TWO 6 STORE_NAME 1 (a) 8 STORE_NAME 0 (b) 10 LOAD_CONST 0 (None) 12 RETURN_VALUE
可见,在 Python 虚拟机的栈上,我们按照表达式右侧的 b,a
的顺序,先后压入计算栈中,然后用一个重要指令 ROT_TWO
,这个操作交换了 a
和 b
的位置,最后 STORE_NAME
操作将栈顶的两个元素先后弹出,传递给 a
和 b
元素。
栈的特性是先进后出(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();
}
代码比较简单,我们用 TOP
和 SECOND
宏获取了栈上的 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_NAME 3 LOAD_NAME 6 LOAD_NAME 9 LOAD_NAME 12 BUILD_TUPLE 15 UNPACK_SEQUENCE 18 STORE_NAME 21 STORE_NAME 24 STORE_NAME 27 STORE_NAME 30 LOAD_CONST 33 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 程序员肯定知道 a,b = b,a,这句话用来交换两个变量.相较于其它语言需要引入一个 temp 来临时存储变量的做法,Python 的这种写法无疑非常优雅. 佶屈聱牙的 C 写法: i ...
- python学习-综合练习六(列表翻转、变量交换、复制列表、元素求和(lambda和递归)、字典排序、字典pop)
文章目录 列表翻转 变量交换 复制列表 元素求和 字典排序 字典pop 说明:本篇博文的知识点大部分来自 Python3 实例 列表翻转 列表翻转就是把列表的值按顺序左移,逻辑还是挺简单的. 我之前的 ...
- Python基础语法-Python,Java,C++变量互换值的区别
两个变量交换数值 C++ #include"iostream" using namespace std; int main(void){int a = 1;int b = 2;in ...
- Python基础-高级变量类型
Python基础-高级变量类型 1.高级变量类型 """ 数值型:int float bool compex 非数值型:str list set dict tuple & ...
- Python 多变量赋值实现“交换”
1. 两个变量交换 我们知道,Python 的对象有三要素:id(对象在内存中的地址).type(对象的数据类型).value(对象的值):而变量没有这三要素. 关于 "引用&quo ...
- python数字切片_剖析python切片「:」「::-1」「-1::」
剖析python切片[:][::-1][-1::] 我们在面试python相关职位时,总是会碰到一些面试python切片的试题,另外在解决某些问题时也经常会用到切片操作,因此勇哥在这里给大家详细介绍p ...
- 【转载】 Python动态生成变量
用Python循环创建多个变量, 如创建 a1= .a2= .a3= .a4= .a5= 或 self.a1= .self.a2= . self.a3= 一. 可以通 ...
- 硬核!Python 四种变量的代码对象和反汇编分析
作者 | 大奎 整理 | 阳哥 来源丨Python数据之道 在Python基础的学习过程中,对变量和参数的理解有助于我们从更基础层面了解Python语言的运行.在这个过程中,还是有不少冷门和细节的地方 ...
- 深入解析Python中的变量和赋值运算符
Python 变量类型 变量存储在内存中的值.这就意味着在创建变量时会在内存中开辟一个空间. 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中. 因此,变量可以指定不同的数据 ...
最新文章
- R语言可视化绘制基本图形
- 期末总结:LINUX内核分析与设计期末总结
- hdu-2544(简单djikstra)
- 学生电脑哪个牌子好_常亮补光灯哪个牌子好服务至上
- 我那个37岁的大神朋友,后续
- Linux系统的Redis的安装与运行
- 二进制和格雷码之间的转换
- lg空调代码大全解决_LG空调故障代码大全-kg空调维修-lg空调维修手册
- [ZT]大型企业局域网安全解决方案
- 【第三课】Arcgis软件详细介绍
- 怎么进行企业工商信息查询?
- 微分几何笔记(1)——参数曲线、内积、外积
- 解决mac压缩包在windows下解压乱码问题。
- H5游戏开发包括哪些游戏类型
- PaddlePaddle运行时出现EnforceNotMet: Enforce failed错误
- 02 汇编语言的函数
- Arduino MEGA2560与蓝牙的通信(玄学)问题
- 竟然是 300 万的诈骗案!
- MAC (M1) 使用homebrew 安装 Mongodb (推荐简单操作)
- 第24篇-极某壁纸结果参数分析