Python 为什么只需一条语句“a,b=b,a”,就能直接交换两个变量?

简介: Python 能在一条语句中实现多重赋值,这是利用了序列解包的特性Python 能在一条语句中实现变量交换,不需引入中间变量,在变量数少于 4 个时(3.8 版本起是少于 5 个),CPython 是利用了 ROT_* 指令来交换栈中的元素,当变量数超出时,则是利用了序列解包的特性。序列解包是 Python 的一大特性,但是在本文的例子中,CPython 解释器在小小的操作中还提供了几个优化的指令,这绝对会超出大多数人的认知

从接触 Python 时起,我就觉得 Python 的元组解包(unpacking)挺有意思,非常简洁好用。

最显而易见的例子就是多重赋值,即在一条语句中同时给多个变量赋值:

>>> x, y = 1, 2
>>> print(x, y)  # 结果:1 2
复制代码

在此例中,赋值操作符“=”号的右侧的两个数字会被存入到一个元组中,即变成 (1,2),然后再被解包,依次赋值给“=”号左侧的两个变量。

如果我们直接写x = 1,2 ,然后打印出 x,或者在“=”号右侧写成一个元组,就能证实到这一点:

>>> x = 1, 2
>>> print(x)     # 结果:(1, 2)
>>> x, y = (1, 2)
>>> print(x, y)  # 结果:1 2
复制代码

一些博客或公众号文章在介绍到这个特性时,通常会顺着举一个例子,即基于两个变量,直接交换它们的值:

>>> x, y = 1, 2
>>> x, y = y, x
>>> print(x, y) # 结果:2 1
复制代码

一般而言,交换两个变量的操作需要引入第三个变量。道理很简单,如果要交换两个杯子中所装的水,自然会需要第三个容器作为中转。

然而,Python 的写法并不需要借助中间变量,它的形式就跟前面的解包赋值一样。正因为这个形式相似,很多人就误以为 Python 的变量交换操作也是基于解包操作。

但是,事实是否如此呢?

我搜索了一番,发现有人试图回答过这个问题,但是他们的回答基本不够全面。(当然,有不少是错误的答案,还有更多人只是知其然,却从未想过要知其所以然)

先把本文的答案放出来吧:Python 的交换变量操作不完全基于解包操作,有时候是,有时候不是!

有没有觉得这个答案很神奇呢?是不是闻所未闻?!

到底怎么回事呢?先来看看标题中最简单的两个变量的情况,我们上dis 大杀器看看编译的字节码:

beb54aef82b7ea001fdcb0b127dbe1f.png

上图开了两个窗口,可以方便比较“a,b=b,a”与“a,b=1,2”的不同:

  • “a,b=b,a”操作:两个 LOAD_FAST 是从局部作用域中读取变量的引用,并存入栈中,接着是最关键的 ROT_TWO 操作,它会交换两个变量的引用值,然后两个 STORE_FAST 是将栈中的变量写入局部作用域中。
  • “a,b=1,2”操作:第一步 LOAD_CONST 把“=”号右侧的两个数字作为元组放到栈中,第二步 UNPACK_SEQUENCE 是序列解包,接着把解包结果写入局部作用域的变量上。

很明显,形式相似的两种写法实际上完成的操作并不相同。在交换变量的操作中,并没有装包和解包的步骤!

ROT_TWO 指令是 CPython 解释器实现的对于栈顶两个元素的快捷操作,改变它们指向的引用对象。

还有两个类似的指令是 ROT_THREE 和 ROT_FOUR,分别是快捷交换三和四个变量(摘自:ceval.c 文件,最新的 3.9 分支):

95fa3d5d7fe979dd1fa394c58060e0c.png

预定义的栈顶操作如下:

1c8f220a95bf680ed9456b952e3926f.png

查看官方文档中对于这几个指令的解释,其中 ROT_FOUR 是 3.8 版本新加的:

  • ROT_TWO
    Swaps the two top-most stack items.
  • ROT_THREE
    Lifts second and third stack item one position up, moves top down to position three.
  • ROT_FOUR
    Lifts second, third and forth stack items one position up, moves top down to position four. New in version 3.8.

CPython 应该是以为这几种变量的交换操作很常见,因此才提供了专门的优化指令。就像 [-5,256] 这些小整数被预先放到了整数池里一样。

对于更多变量的交换操作,实际上则会用到前面说的解包操作:

637debc69ffc5b2dcde7a139dc1ce51.png

截图中的 BUILD_TUPLE 指令会将给定数量的栈顶元素创建成元组,然后被 UNPACK_SEQUENCE 指令解包,再依次赋值。

值得一提的是,此处之所以比前面的“a,b=1,2”多出一个 build 操作,是因为每个变量的 LOAD_FAST 需要先单独入栈,无法直接被组合成 LOAD_CONST 入栈。也就是说,“=”号右侧有变量时,不会出现前文中的  LOAD_CONST 一个元组的情况。

最后还有一个值得一提的细节,那几个指令是跟栈中元素的数量有关,而不是跟赋值语句中实际交换的变量数有关。看一个例子就明白了:

97193c1f019ea5fd99cff3887c3ce24.png

分析至此,你应该明白前文中的结论是怎么回事了吧?

我们稍微总结一下:

  • Python 能在一条语句中实现多重赋值,这是利用了序列解包的特性
  • Python 能在一条语句中实现变量交换,不需引入中间变量,在变量数少于 4 个时(3.8 版本起是少于 5 个),CPython 是利用了 ROT_* 指令来交换栈中的元素,当变量数超出时,则是利用了序列解包的特性。
  • 序列解包是 Python 的一大特性,但是在本文的例子中,CPython 解释器在小小的操作中还提供了几个优化的指令,这绝对会超出大多数人的认知

如果你觉得本文分析得不错,那你应该会喜欢这些文章:

1、Python为什么使用缩进来划分代码块?

2、Python 的缩进是不是反人类的设计?

3、Python 为什么不用分号作语句终止符?

4、Python 为什么没有 main 函数?为什么我不推荐写 main 函数?

5、Python 为什么推荐蛇形命名法?

6、Python 为什么不支持 i++ 自增语法,不提供 ++ 操作符?


目录
相关文章
|
18天前
|
Shell Python
python 和shell 变量互相传递
python 和shell 变量互相传递
24 0
|
1月前
|
存储 Java Python
python变量、常量、数据类型
python变量、常量、数据类型
|
1天前
|
Python
在Python中,全局变量和局部变量是两种不同类型的变量
Python中的全局变量在函数外部定义,作用域覆盖整个程序,生命周期从开始到结束。局部变量仅限于函数内部,生命周期从调用到返回。在函数内修改全局变量需用`global`关键字声明,否则会创建局部变量。
11 3
|
3天前
|
Python
python 变量的定义和使用详解
python 变量的定义和使用详解
8 0
|
3天前
|
Java C# 开发者
Python 中的类型注解是一种用于描述变量、函数参数和返回值预期类型的机制
Python的类型注解提升代码可读性和可维护性,虽非强制,但利于静态类型检查(如Mypy)。包括:变量注解、函数参数和返回值注解,使用内置或`typing`模块的复杂类型,自定义类型注解,以及泛型模拟。类型注解可在变量声明、函数定义和注释中使用,帮助避免类型错误,提高开发效率。
16 6
|
5天前
|
Python
python变量未定义(NameError)
【5月更文挑战第1天】
15 1
|
13天前
|
Python
python函数的返回值、嵌套方式以及函数中的变量(二)
python函数的返回值、嵌套方式以及函数中的变量(二)
|
13天前
|
存储 Python 容器
python函数的返回值、嵌套方式以及函数中的变量(一)
python函数的返回值、嵌套方式以及函数中的变量(一)
|
13天前
|
运维 监控 Serverless
Serverless 应用引擎产品使用之阿里函数计算中在自定义环境下用debian10运行django,用官方层的python3.9,配置好环境变量后发现自定义层的django找不到了如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
22 3
|
17天前
|
机器学习/深度学习 存储 数据挖掘
Python从入门到精通——学习基础语法和数据类型 1.2.1变量、整数、浮点数、字符串、布尔值、列表、元组、字典和集合。
Python从入门到精通——学习基础语法和数据类型 1.2.1变量、整数、浮点数、字符串、布尔值、列表、元组、字典和集合。