脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Python - Python 如何实现变量交换

Python 如何实现变量交换

2022-09-05 11:01大奎 Python

这篇文章主要介绍了Python 如何实现变量交换,Python 程序员肯定知道 a,b = b,a,这句话用来交换两个变量。相较于其它语言需要引入一个 temp 来临时存储变量的做法,Python 的这种写法无疑非常优雅,下面我们来看看具体的实现过程吧

简洁优雅的 C 写法:

?
1
2
3
4
5
6
int a = 1
int b = 2
int temp; 
temp = a; 
a = b; 
b = temp; 

简洁优雅的 Python 写法:

?
1
2
a,b = 1,2
a,bb = b,a 

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

比较通俗的说法

最常见的解释是:

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

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

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

从字节码一窥交换变量:

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

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

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

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

?
1
2
3
4
5
6
7
8
9
>>> import dis 
>>> dis.dis("a,bb=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 的操作。

?
1
2
3
4
5
6
7
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 宏把值写入栈中。以此完成交换栈顶元素的操作。

求值顺序的奇怪现象!

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

?
1
2
3
4
5
6
7
8
>>> 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 这种操作了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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_* 操作来优化的。

?
1
2
3
>>>import opcode 
>>>opcode.opmap["ROT_THREE"
3

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

到此这篇关于Python 如何实现变量交换的文章就介绍到这了,更多相关Python 变量交换内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://developer.51cto.com/article/699453.html

延伸 · 阅读

精彩推荐
  • Pythonnumpy实现RNN原理实现

    numpy实现RNN原理实现

    这篇文章主要介绍了numpy实现RNN原理实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小...

    J k l4112021-09-13
  • Pythonpython安装mysql的依赖包mysql-python操作

    python安装mysql的依赖包mysql-python操作

    这篇文章主要介绍了python安装mysql的依赖包mysql-python操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    lee_moonj4712021-08-20
  • Python简单的抓取淘宝图片的Python爬虫

    简单的抓取淘宝图片的Python爬虫

    这篇文章主要介绍了简单的抓取淘宝图片的Python爬虫,方法比较简单,推荐给小伙伴们参考下。 ...

    脚本之家12482020-05-17
  • Pythondjango orm 通过related_name反向查询的方法

    django orm 通过related_name反向查询的方法

    今天小编就为大家分享一篇django orm 通过related_name反向查询的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    喝完这杯还有一箱4132021-04-30
  • Pythontensorflow更改变量的值实例

    tensorflow更改变量的值实例

    今天小编就为大家分享一篇tensorflow更改变量的值实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    勿在浮沙筑高台LS8722021-03-23
  • Python浅谈DataFrame和SparkSql取值误区

    浅谈DataFrame和SparkSql取值误区

    今天小编就为大家分享一篇浅谈DataFrame和SparkSql取值误区,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    silentwolfyh4942021-03-02
  • Pythonpython使用paramiko实现远程拷贝文件的方法

    python使用paramiko实现远程拷贝文件的方法

    这篇文章主要介绍了python使用paramiko实现远程拷贝文件的方法,分析了paramiko库的安装以及远程下载文件的实现技巧,需要的朋友可以参考下...

    mo_guang7212020-08-20
  • Python基于pyinstaller超级加密操作(加壳和转c)

    基于pyinstaller超级加密操作(加壳和转c)

    这篇文章主要介绍了基于pyinstaller超级加密操作 (加壳和转c),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    追梦小狂魔9982021-09-16