Skip to content
Go back

Python 变量交换的底层实现

Edit page

使用 Python 交换两个变量,通常有一种非常方便且 Pythonic 的方式:

a, b = b, a

而通常其他编程语言 (例如C/C++) 需要显式使用一个临时变量来存储中间值:

int temp;

temp = a;
a = b;
b = temp;

那么在 Python 中,通过 a, b = b, a 是如何实现变量交换的呢?

字节码对比

直接使用 Python 自带的 dis 模块来查看底层的字节码指令:

以下字节码基于 Python 3.13 运行,该版本引入了针对变量交换的特化指令优化。旧版本 Python 通常使用 ROT_TWO 指令。

import dis

def swap_with_temp():
    a = 1
    b = 2

    t = a
    a = b
    b = t

def swap_pythonic():
    a = 1
    b = 2

    a, b = b, a

print("--- 使用临时变量 (temp) 的字节码 ---")
dis.dis(swap_with_temp)

print("\n--- 使用 a, b = b, a 的字节码 ---")
dis.dis(swap_pythonic)

使用 Python 3.13 运行如上代码,可以观察到不同的结果:

--- 使用临时变量 (temp) 的字节码 ---
  3           RESUME                   0

  4           LOAD_CONST               1 (1)
              STORE_FAST               0 (a)

  5           LOAD_CONST               2 (2)
              STORE_FAST               1 (b)

  7           LOAD_FAST                0 (a)
              STORE_FAST               2 (t)

  8           LOAD_FAST                1 (b)
              STORE_FAST               0 (a)

  9           LOAD_FAST                2 (t)
              STORE_FAST               1 (b)
              RETURN_CONST             0 (None)

--- 使用 a, b = b, a 的字节码 ---
 11           RESUME                   0

 12           LOAD_CONST               1 (1)
              STORE_FAST               0 (a)

 13           LOAD_CONST               2 (2)
              STORE_FAST               1 (b)

 15           LOAD_FAST_LOAD_FAST     16 (b, a)
              STORE_FAST_STORE_FAST   16 (b, a)
              RETURN_CONST             0 (None)

可以看到,使用临时变量的方法需要额外的存储和读取操作(STORE_FAST 2 (t)LOAD_FAST 2 (t)),而 Pythonic 的方式只需要两条合并指令:

  1. LOAD_FAST_LOAD_FAST - 一次性加载两个变量
  2. STORE_FAST_STORE_FAST - 一次性存储两个变量

指令详解

LOAD_FAST_LOAD_FAST

LOAD_FAST_LOAD_FAST 是一条合并指令,相当于连续执行两次 LOAD_FAST,其执行步骤是:

  1. 解码出第一个变量索引:index1 = var_nums >> 4(高4位)
  2. 解码出第二个变量索引:index2 = var_nums & 15(低4位)
  3. co_varnames[index1] 对应的值压栈
  4. co_varnames[index2] 对应的值压栈

STORE_FAST_STORE_FAST

STORE_FAST_STORE_FAST 的执行步骤是:

  1. 解码出第一个变量索引:index1 = var_nums >> 4(高4位)
  2. 解码出第二个变量索引:index2 = var_nums & 15(低4位)
  3. 把栈顶 STACK[-1] 存入 co_varnames[index1]
  4. 把次栈顶 STACK[-2] 存入 co_varnames[index2]

参数编码

Python 字节码的设计由操作码参数组成,无法携带两个独立的参数,所以将两个变量索引合并到一个字节 var_nums 中。

co_varnames 是 Python 存储局部变量名的一个元组。

在本例中,co_varnames('a', 'b'),即:

对于 LOAD_FAST_LOAD_FAST,我们需要加载 b(索引 1)和 a(索引 0),所以参数计算为:

var_nums = (1 << 4) | 0 = 16

将索引 1 和索引 0 分别存储到高四位和低四位。

执行过程

现在跟踪一下完整的执行过程:

步骤 1:执行 LOAD_FAST_LOAD_FAST 16 (b, a)

此时栈的结构是:

--- top
| 1 |  <-- 原变量 a 的值 (由第二个 LOAD 压入,索引0)
-----
| 2 |  <-- 原变量 b 的值 (由第一个 LOAD 压入,索引1)
--- bottom

步骤 2:执行 STORE_FAST_STORE_FAST 16 (b, a)

值得注意的是:我们的源代码左侧是 a, b,而字节码参数却是 16(代表 b, a),这是由指令的定义和栈中数据的顺序决定的:

  1. 该指令将栈顶(Top)的值存入高4位索引(即 b)。当前栈顶是 1(原 a 的值),所以 b 被赋值为 1
  2. 该指令将次栈顶(Second)的值存入低4位索引(即 a)。当前次栈顶是 2(原 b 的值),所以 a 被赋值为 2

正是通过这种巧妙的参数构造和栈操作顺序,Python 在一条指令内完成了两个变量值的”交叉”写入,实现了交换。

性能优化与限制

由于 LOAD_FAST_LOAD_FASTSTORE_FAST_STORE_FAST 的参数 var_nums 是一个字节(8 位),高 4 位和低 4 位分别保存了两个变量索引,所以每个索引最多只能表示 0 - 15(共 16 个值)。

这意味着:当一个函数的局部变量超过 16 个时,Python 将退回到普通的 LOAD_FASTSTORE_FAST 指令,无法使用这种合并指令优化。好在大部分函数都不会超过 16 个局部变量,所以这个优化在大多数情况下都能生效。

总结

Python 通过 a, b = b, a 实现变量交换时,底层使用了特化的合并指令:


Edit page
Share this post on:

Next Post
2025 Week 52 周报