当前位置: 首页 > news >正文

关于python的赋值说法_Python中tuple+=赋值的四个问题

最近偶尔翻看Fluent Python,遇到有意思的东西就记下来. 下面的是在PyCon2013上提出的一个关于tuple的Augmented Assignment也就是增量赋值的一个问题。 并且基于此问题, 又引申出3个变种问题.

问题

首先看第一个问题, 如下面的代码段:

>>> t = (1,2, [30,40])

>>> t[2] += [50,60]

会产生什么结果呢? 给出了四个选项:

t 变成 [1,2, [30,40,50,60]

TypeError is raised with the message 'tuple' object does not support item assignment

Neither 1 nor 2

Both 1 and 2

按照之前的理解, tuple里面的元素是不能被修改的,因此会选2. 如果真是这样的话,这篇笔记就没必要了,Fluent Python中也就不会拿出一节来讲了。 正确答案是4

>>> t = (1,2,[30,40])

>>> t[2] += [50,60]

Traceback (most recent call last):

File "", line 1, in

TypeError: 'tuple' object does not support item assignment

>>> t

(1, 2, [30, 40, 50, 60])

问题来了,为什么异常都出来了, t还是变了?

再看第二种情况,稍微变化一下,将+=变为=:

>>> t = (1,2, [30,40])

>>> t[2] = [50,60]

结果就成酱紫了:

>>> t = (1,2, [30,40])

>>> t[2] = [50,60]

Traceback (most recent call last):

File "", line 1, in

TypeError: 'tuple' object does not support item assignment

>>> t

(1, 2, [30, 40])

再看第三种情况,只把+=换为extend或者append,:

>>> t = (1, 2, [30,40])

>>> t[2].extend([50,60])

>>> t

(1, 2, [30, 40, 50, 60])

>>> t[2].append(70)

>>> t

(1, 2, [30, 40, 50, 60, 70])

又正常了,没抛出异常?

最后第四种情况, 用变量的形式:

>>> a = [30,40]

>>> t = (1, 2, a)

>>> a+=[50,60]

>>> a

[30, 40, 50, 60]

>>> t

(1, 2, [30, 40, 50, 60])

>>> t[2] += [70,80]

Traceback (most recent call last):

File "", line 1, in

TypeError: 'tuple' object does not support item assignment

>>> t

(1, 2, [30, 40, 50, 60, 70, 80])

又是一种情况, 下面就探究一下其中的原因.

原因

首先需要重温+=这个运算符,如a+=b:

对于可变对象(mutable object)如list, +=操作的结果会直接在a对应的变量进行修改,而a对应的地址不变.

对于不可变对象(imutable object)如tuple, +=则是等价于a = a+b 会产生新的变量,然后绑定到a上而已.

如下代码段, 可以看出来:

>>> a = [1,2,3]

>>> id(a)

53430752

>>> a+=[4,5]

>>> a

[1, 2, 3, 4, 5]

>>> id(a)

53430752 # 地址没有变化

>>> b = (1,2,3)

>>> id(b)

49134888

>>> b += (4,5)

>>> b

(1, 2, 3, 4, 5)

>>> id(b)

48560912 # 地址变化了

此外还需要注意的是, python中的tuple作为不可变对象, 也就是我们平时说的元素不能改变, 实际上从报错信息TypeError: 'tuple' object does not support item assignment来看, 更准确的说法是指其中的元素不支持赋值操作=(assignment).

先看最简单的第二种情况, 它的结果是符合我们的预期, 因为=产生了assign的操作.(在由一个例子到python的名字空间 中指出了赋值操作=就是创建新的变量), 因此s[2]=[50,60]就会抛出异常.

再看第三种情况,包含extend/append的, 结果tuple中的列表值发生了变化,但是没有异常抛出. 这个其实也相对容易理解. 因为我们知道tuple中存储的其实是元素所对应的地址(id), 因此如果没有赋值操作且tuple中的元素的id不变,即可,而list.extend/append只是修改了列表的元素,而列表本身id并没有变化,看看下面的例子:

>>> a=(1,2,[30,40])

>>> id(a[2])

140628739513736

>>> a[2].extend([50,60])

>>> a

(1, 2, [30, 40, 50, 60])

>>> id(a[2])

140628739513736

目前解决了第二个和第三个问题, 先梳理一下, 其实就是两点:

tuple内部的元素不支持赋值操作

在第一条的基础上, 如果元素的id没有变化, 元素其实是可以改变的.

现在再来看最初的第一个问题: t[2] += [50,60] 按照上面的结论, 不应该抛异常啊,因为在我们看来+= 对于可变对象t[2]来说, 属于in-place操作,也就是直接修改自身的内容, id并不变, 确认下id并没有变化:

>>> a=(1,2,[30,40])

>>> id(a[2])

140628739587392

>>> a[2]+=[50,60]

Traceback (most recent call last):

File "", line 1, in

TypeError: 'tuple' object does not support item assignment

>>> a

(1, 2, [30, 40, 50, 60])

>>> id(a[2]) # ID 并没有发生改变

140628739587392

跟第三个问题仅仅从t[2].extend改成了t[2]+=, 就抛出异常了,所以问题应该是出在+=上了.

下面用dis模块看看它俩执行的步骤:

对下面的代码块执行dis:

t = (1,2, [30,40])

t[2] += [50,60]

t[2].extend([70, 80])

执行python -m dis test.py,结果如下,下面只保留第2,3行代码的执行过程,以及关键步骤的注释如下:

2 21 LOAD_NAME 0 (t)

24 LOAD_CONST 1 (2)

27 DUP_TOPX 2

30 BINARY_SUBSCR

31 LOAD_CONST 4 (50)

34 LOAD_CONST 5 (60)

37 BUILD_LIST 2

40 INPLACE_ADD

41 ROT_THREE

42 STORE_SUBSCR

3 43 LOAD_NAME 0 (t)

46 LOAD_CONST 1 (2)

49 BINARY_SUBSCR

50 LOAD_ATTR 1 (extend)

53 LOAD_CONST 6 (70)

56 LOAD_CONST 7 (80)

59 BUILD_LIST 2

62 CALL_FUNCTION 1

65 POP_TOP

66 LOAD_CONST 8 (None)

69 RETURN_VALUE

解释一下关键的语句:

30 BINARY_SUBSCR: 表示将t[2]的值放在TOS(Top of Stack),这里是指[30, 40]这个列表

40 INPLACE_ADD: 表示TOS += [50,60] 执行这一步是可以成功的,修改了TOS的列表为[30,40,50,60]

42 STORE_SUBSCR: 表示s[2] = TOS 问题就出在这里了,这里产生了一个赋值操作,因此会抛异常!但是上述对列表的修改已经完成, 这也就解释了开篇的第一个问题。

再看extend的过程,前面都一样,只有这一行:

62 CALL_FUNCTION: 这个直接调用内置extend函数完成了对原列表的修改,其中并没有assign操作,因此可以正常执行。

现在逐渐清晰了, 换句话说,+=并不是原子操作,相当于下面的两步:

t[2].extend([50,60])

t[2] = t[2]

第一步可以正确执行,但是第二步有了=,肯定会抛异常的。 同样这也可以解释在使用+=的时候,为何t[2]的id明明没有变化,但是仍然抛出异常了。

现在用一句话总结下:

tuple中元素不支持assign操作,但是对于那些是可变对象的元素如列表,字典等,在没有assign操作的基础上,比如一些in-place操作,是可以修改内容的

可以用第四个问题来简单验证一下,使用一个指向[30,40]的名称a来作为元素的值,然后对a做in-place的修改,其中并没有涉及到对tuple的assign操作,那肯定是正常执行的。

总结

这个问题其实以前也就遇到过,但是没想过具体的原理,后来翻书的时候又看到了, 于是花了点时间把这一个系列查了部分资料以及结合自己的理解都整理了出来, 算是饭后茶点吧, 不严谨的地方烦请指出.

部分参考如下:

相关文章:

  • python如何截取日期中的月份_python – 从给定日期开始提取日,月和年的...
  • python3 beautifulsoup_(转载) python3: beautifulsoup的使用
  • 为什么除零错会导致程序崩溃_Firefox 76.0.1紧急发布:修复导致部分扩展程序崩溃问题...
  • python3 zipfile解压出错_解决python3中解压zip文件是文件名乱码的问题
  • jmeter 线程执行顺序_面试官:线程顺序执行,这么多答案你都答不上来?
  • python可变参数的特点_Java可变参数 Python可变参数 Scala可变参数
  • mysql 多列合并为一列_mysql面试名词聚簇索引、二级索引、最左匹配、覆盖索引、回表
  • 备份类型 事务日志_InnoDB事务日志redo log和undo log详解
  • 被呼叫方拒绝接收呼叫_外包呼叫中心的“前世今生”培训课件6/12
  • 检测到有程序正在访问网络_花旗银行、eBay等网站窃取访问者隐私信息 Behave可监测网站行为...
  • 对象删除某个属性_了解Pandas索引对象:索引对象介绍,索引重建方法等
  • c4.5决策树算法python_决策树之python实现C4.5算法
  • wpf将文字转化为图形_photoshop 2020将任意形状或文本转化为图框并填充图像实例...
  • python清洗数据 food ounces animal_利用Python进行数据分析-Pandas(第四部分-数据清洗和准备)...
  • 高级灰rgb数值_美商海盗船七夕巨献:教你用RGB辨认口红色号
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • Golang-长连接-状态推送
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • js作用域和this的理解
  • mac修复ab及siege安装
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • tweak 支持第三方库
  • Vue2 SSR 的优化之旅
  • - 概述 - 《设计模式(极简c++版)》
  • 解析带emoji和链接的聊天系统消息
  • 设计模式 开闭原则
  • 深度解析利用ES6进行Promise封装总结
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 我有几个粽子,和一个故事
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • No resource identifier found for attribute,RxJava之zip操作符
  • 积累各种好的链接
  • 说说我为什么看好Spring Cloud Alibaba
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • ​卜东波研究员:高观点下的少儿计算思维
  • #{} 和 ${}区别
  • #13 yum、编译安装与sed命令的使用
  • #if和#ifdef区别
  • ()、[]、{}、(())、[[]]等各种括号的使用
  • (0)Nginx 功能特性
  • (floyd+补集) poj 3275
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (分布式缓存)Redis哨兵
  • (论文阅读30/100)Convolutional Pose Machines
  • (生成器)yield与(迭代器)generator
  • (四)Controller接口控制器详解(三)
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • .net core Swagger 过滤部分Api
  • .NET 材料检测系统崩溃分析
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .NET单元测试
  • .NET委托:一个关于C#的睡前故事