一个关于|一个关于 += 的谜题
原文链接: 一个关于 += 的谜题
今天在看书过程中发现了一个问题,还挺有意思的,分享给大家。
下面两个 Python 表达式会产生什么结果?
t = (1, 2, [3, 4])
t[2] += [5, 6]
给四个备选答案:
t
变成(1, 2, [3, 4, 5, 6])
。- 因为 tuple 不支持对它的元素赋值,所以会抛出
TypeError
异常。 - 以上两个都不是。
- 以上两个都是对的。
但事实上,这道题的正解是 4。
在终端里验证一下:
Python 3.8.2 (default, Oct2 2020, 10:45:42)
[Clang 12.0.0 (clang-1200.0.32.27)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> t = (1, 2, [3, 4])
>>> t[2] += [5, 6]
Traceback (most recent call last):
File "", line 1, in
TypeError: 'tuple' object does not support item assignment
结果是没问题的,
t
被修改了,但是也报错了。还可以在 Python Tutor 上分析一下:
网站地址: https://pythontutor.com/
这个网站可以可视化分析 Python 的运行过程和原理。
执行第一个表达式:
文章图片
执行第二个表达式:
文章图片
为什么会这样呢?可以从两个方面来解释:
一、对象类型 Python 中的对象可以分成两类,可变对象和不可变对象,比如一些内置类型:
- 可变对象:list,set,dict。
- 不可变对象:int,float,bool,string,tuple。
可变对象:
>>> a = [1, 2, 3]
>>> id(a)
2139167246856
>>> b = a
>>> id(b)
2139167246856
>>> a[1] = 4
>>> a
[1, 4, 3]
>>> b
[1, 4, 3]
>>> id(a)
2139167246856
>>> id(b)
2139167246856
可以看到,改变
a
的同时 b
也跟着变,因为他们始终指向同一个地址。不可变对象:
>>> a = (1, 2, 3)
>>> id(a)
2139167074776
>>> b = a
>>> a = (4, 5, 6)
>>> a
(4, 5, 6)
>>> b
(1, 2, 3)
>>> id(a)
2139167075928
>>> id(b)
2139167074776
可以看到,
a
的值改变后,它的地址也发生了变化,而 b
还是原来的地址,并且原地址中的内容也没有发生变化。二、字节码 首先解释一下字节码是什么?
Python 执行程序时会把源码文件编译成字节码文件,存放在 __pycahe 目录内,文件用
.pyc
结尾。之后如果不再修改源码文件,运行时则直接使用 .pyc
文件编译成机器码,这样不但运行速度快,而且支持多个操作系统。字节码,其实就是一种中间代码。
下面用 dis 模块来看一下表达式
s[a] += b
的执行过程:>>> import dis
>>> dis.dis('s[a] += b')
10 LOAD_NAME0 (s)
2 LOAD_NAME1 (a)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_NAME2 (b)
10 INPLACE_ADD
12 ROT_THREE
14 STORE_SUBSCR
16 LOAD_CONST0 (None)
18 RETURN_VALUE
>>>
通过分析字节码,可以看到其中的关键三步:
4 DUP_TOP_TWO
:将s[a]
存入 TOS(Top Of Stack)。10 INPLACE_ADD
:执行TOS += b
,带入到文章开头的表达式,就相当于向t[2]
中添加元素,因为t[2]
是 list,可变对象,所以这一操作没有问题。14 STORE_SUBSCR
:将结果保存回s[a] = TOS
,这相当于将结果重新赋值回t
,由于t
是 tuple,不可变对象,所以报错。
简单总结以下三点:
- 不要把可变对象放在元组里面。
- 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作。
- 查看 Python 的字节码并不难,而且它对我们了解代码背后的运行机制很有帮助。
推荐阅读:
- 计算机经典书籍(含下载方式)
- 技术博客: 硬核后端开发技术干货,内容包括 Python、Django、Docker、Go、Redis、ElasticSearch、Kafka、Linux 等。
- Go 程序员: Go 学习路线图,包括基础专栏,进阶专栏,源码阅读,实战开发,面试刷题,必读书单等一系列资源。
- 面试题汇总: 包括 Python、Go、Redis、MySQL、Kafka、数据结构、算法、编程、网络等各种常考题。
推荐阅读
- Kafka与ELK实现一个日志系统
- 阿帕网几台大型计算机网络,阿帕网-互联网的鼻祖(中) | 午夜猫
- 云数据中心网络遇到的问题_阿帕云(云计算数据中心面临哪些安全挑战())
- 历史上的今天|【历史上的今天】2 月 28 日(阿帕网退役;Quintus 收购 Mustang;同步电流磁芯存储器获得专利)
- C#|C# 提取Word中插入的多媒体文件(视频、音频)
- 80 行代码实现简易 RxJS
- 开箱即用的 Embedding 流水线
- 基于 Kafka 的实时数仓在搜索的实践应用
- 【北亚数据恢复】服务器重装系统后分区消失和分区不可访问的数据恢复案例
- Spring Cloud Alibaba Nacos 的 2 种健康检查机制!