资讯专栏INFORMATION COLUMN

Python中tuple+=赋值的四个问题

Yujiaao / 1145人阅读

摘要:原文链接最近偶尔翻看,遇到有意思的东西就记下来下面的是在上提出的一个关于的也就是增量赋值的一个问题。再看的过程,前面都一样,只有这一行这个直接调用内置函数完成了对原列表的修改,其中并没有操作,因此可以正常执行。

原文链接

最近偶尔翻看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来作为元素的值,然后对ain-place的修改,其中并没有涉及到对tuple的assign操作,那肯定是正常执行的。

总结

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

部分参考如下:

python bugs

python faq

stackoverflow

Fluent Python

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/40753.html

相关文章

  • Python每日小知识(3):list和tuple的使用

    摘要:同样的用上节讲的函数获取元素的个数记住这是获取的是列表个数个数个数重要的事说三遍。用索引访问每个元素的位置,索引是从开始的开始的开始的索引也是有容忍限度的超过了就会原地爆炸报错如果列表中元素个数贼多,想获取后面的元素就要实行曲线救国了。 list和tuple是Python内置的有序集合,一个是可变的,一个是不可变滴;这都不是事,主要是理解一下指向不变。 看图说话: showImg(ht...

    SoapEye 评论0 收藏0
  • Python试水

    摘要:你好,特别注意定义编码格式的这一行代码必须放在第一行或者第二行,一般如果第一行是提示位置的代码,那么定义编码格式的这一行就必须放在第二行,否则依然会提示错误。基本功能是进行成员关系测试和删除重复元素。 一、Hello,Python! 试水 test.py print 你好,Python! #python 2.x python (你好,Python!) #python 3.x 很不幸,第...

    zhjx922 评论0 收藏0
  • Python基础之(五)语句

    摘要:逻辑运算符假设,运算符描述实例布尔与如果为,返回,否则它返回的计算值。布尔或如果是,它返回,否则它返回的计算值。以为例,说明语句。逗号表示打印在同一行本来,在语句中,字符串后面会接一个符号。 运算符 算术运算符 前面已经讲过了四则运算,其中涉及到一些运算符:加减乘除,对应的符号分别是:+ - * /,此外,还有求余数的:%。这些都是算术运算符。其实,算术运算符不止这些。根据中学数...

    alaege 评论0 收藏0
  • python入门

    摘要:零预备知识字符编码计算机只能处理数字,所以为文本需要转化为数字才能被计算机处理,计算机里八个比特作为一个字节,这是数据的存储基础单位。 零、预备知识 0.1 字符编码计算机只能处理数字,所以为文本需要转化为数字才能被计算机处理,计算机里八个比特(bit)作为一个字节(byte),这是数据的存储基础单位。计算机为了处理文本,有以下三种编码方式: ASCII码:只有大小写英文字母,数字...

    xeblog 评论0 收藏0
  • Python基础学习之常用六大数据类型

    摘要:刚开始学习一门编程语言,除了了解运行环境与语言类型之外,最基本还是从该语言的基本数据类型开始学起。六大常用数据类型整数浮点数字符串列表元组字典讲解这些先说一下中的变量与变量名。支持对整数和浮点数直接进行四则混合运算。 刚开始学习一门编程语言,除了了解运行环境与语言类型之外,最基本还是从该语言的基本数据类型开始学起。 Python六大常用数据类型: int 整数 floa...

    GT 评论0 收藏0

发表评论

0条评论

Yujiaao

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<