专栏名称: Crossin的编程教室
编程世界的新手村。 这里有可能是最简单的 Python 入门教程。 每天5分钟,轻松学编程。
目录
相关文章推荐
中国电建  ·  海南天角潭水利枢纽工程正式投产发电 ·  6 小时前  
中国电建  ·  藏不住了!电建人的MBTI性格类型太明显了 ·  昨天  
51好读  ›  专栏  ›  Crossin的编程教室

Python变量交换的底层实现原理

Crossin的编程教室  · 公众号  ·  · 2024-12-09 13:49

正文

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







请到「今天看啥」查看全文