>>> from env_helper import info; info()
页面更新时间: 2023-08-13 11:54:12
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-11-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

2.2. 数据交换值的时候不必使用中间变量

交换两个变量的值,大家熟悉的代码如下:

>>> x='f'
>>> y='2'
>>> print('x='+x+'\ny='+y)
x=f
y=2
>>> temp = x
>>> x= y
>>> y = temp
>>> print('x='+x+'\ny='+y)
x=2
y=f

实际上,在Python中还有更简单、更Pythonic的实现方式,代码如下:

>>> print('交换前x='+x+'\ny='+y)
>>> x,y = y,x
>>> print('交换后x='+x+'\ny='+y)
交换前x=2
y=f
交换后x=f
y=2

上面的实现方式不需要借助任何中间变量并且能够获取更好的性能。 我们来简单测试一下。

>>> from timeit import Timer
>>> Timer('temp =x;x= y;y = temp','x=2;y =3').timeit()
0.018912468000053195
>>> Timer('x, y = y, x','x=2;y=3').timeit()
0.01583487999960198

从测试结果可以看出,第二种方式耗费的时间更少,并且由于不需要借助中间变量,代码更为简洁,是值得推荐的一种方式。 那么,为什么第二种方式可以做到更优呢?这要从 Python 表达式计算的顺序说起。 一般情况下Python表达式的计算顺序是从左到右,但遇到表达式赋值的时候表达式右边的操作数先于左边的操作数计算, 因此表达式 expr3, expr4 = expr1, expr2 的计算顺序是 exprl, expr2—>expr3, expr4 。 因此对于表达式 x, y = y, x ,其在内存中执行的顺序如下:

  1. 先计算右边的表达式 y,x , 因此先在内存中创建元组 (y,x) , 其标示符和值分别为 yx 及其对应的值。其中 yx 是在初始化时已经存在于内存中的对象。

  2. 计算表达式左边的值并进行赋值,元组被依次分配给左边的标示符,通过解压缩 (unpacking), 元组第一标识符(为 y )分配给左边第一个元素(此时为 x ),元组第二个标识符 (为 x )分配给第二个元素(此时为 y ),从而达到 xy 值交换的目的。

更深入一点我们从Python生成的字节码来分析。Python的字节码是一种类似汇编指令 的中间语言, 但是一个字节码指令并不是对应一个机器指令。

我们通过以下 dis 模块的来进 行分析:

>>> import dis
>>> def swap1():
>>>     x=2
>>>     y=3
>>>     x,y=y,x
>>> def swap2():
>>>     x=2
>>>     y=3
>>>     temp=x
>>>     x=y
>>>     y=temp
>>> print('swap1():')
swap1():
>>> dis.dis(swap1)
2           0 RESUME                   0

3           2 LOAD_CONST               1 (2)
            4 STORE_FAST               0 (x)

4           6 LOAD_CONST               2 (3)
            8 STORE_FAST               1 (y)

5          10 LOAD_FAST                1 (y)
           12 LOAD_FAST                0 (x)
           14 STORE_FAST               1 (y)
           16 STORE_FAST               0 (x)
           18 LOAD_CONST               0 (None)
           20 RETURN_VALUE
>>> print('swap2():')
swap2():
>>> dis.dis(swap2)
 6           0 RESUME                   0

 7           2 LOAD_CONST               1 (2)
             4 STORE_FAST               0 (x)

 8           6 LOAD_CONST               2 (3)
             8 STORE_FAST               1 (y)

 9          10 LOAD_FAST                0 (x)
            12 STORE_FAST               2 (temp)

10          14 LOAD_FAST                1 (y)
            16 STORE_FAST               0 (x)

11          18 LOAD_FAST                2 (temp)
            20 STORE_FAST               1 (y)
            22 LOAD_CONST               0 (None)
            24 RETURN_VALUE

通过字节码可以看出,区别主要集中在 swap 函数的第4行和 swap2 函数的第4 ~ 6行代码, 其中 swapl 的第4行代码对应的字节码中有2个LOAD_FAST指令、2个STORE_ FAST指令和1个R〇T_TWO指令, 而swap2函数对应的第4 ~ 6行代码中共生成了 3个 LOAD_FAST指令和3个STORE_FAST指令P而指令ROT_TWO的主要作用是交换两个找 的最顶层元素, 它比执行一个LOAD_FAST+STORE_FAST指令更快。