《流畅的python》读书笔记(7)

2.6 序列的增量赋值

增量赋值运算符 += 和 *= 的表现取决于它们的第一个操作对象+= 背后的特殊方法是 iadd (用于“就地加法”)但是如果一个类没有实现这个方法的话,Python 会退一步调用 __add__如果 a 实现了 iadd 方法,就会调用这个方法。
同时对可变序列(例如list、bytearray 和 array.array)来说,a 会就地改动,就像调用了 a.extend(b)一样。但是如果 a 没有实现 iadd 的话,a += b 这个表达式的效果就变得跟 a = a+ b 一样了:首先计算 a + b,得到一个新的对象,然后赋值给 a。
也就是说,在这个表达式中,变量名会不会被关联到新的对象,完全取决于这个类型有没有实现 iadd 这个方法。
总体来讲,可变序列一般都实现了 iadd 方法,因此 += 是就地加法。而不可变序列根本就不支持这个操作,对这个方法的实现也就无从谈起。上面所说的这些关于 += 的概念也适用于 *=,不同的是,后者相对应的是 imul

一个列子

#  *= 在可变和不可变序列上的作用

# 列表是一个可变序列
l = [1, 2, 3]
print(id(l))
# 1685058314824
l *= 2
print(l)
# [1, 2, 3, 1, 2, 3]
print(id(l))
# 1685058314824

# 可以发现 对于可变序列 在使用*=运算符后 其id不变

# 元组是不可变的序列
x = (1, 2, 3)
print(id(x))
# 1364365308120
x *= 2
print(x)
# (1, 2, 3, 1, 2, 3)
print(id(x))
# 1364363843528

# 对于不可变的序列进行 *= 之后 新的对象被创建了

但是 注意:str 是一个例外,因为对字符串做 += 实在是太普遍了,所以 CPython 对它做了优化。
为 str 初始化内存的时候,程序会为它留出额外的可扩展空间,因此进行增量操作的时候,
并不会涉及复制原有字符串到新位置这类操作

一个关于+=的谜题

看到这个例子我也很懵圈。
且看书中阐述
t = (1, 2, [30, 40])
t[2] += [50,60]
备选答案:
a. t 变成 (1, 2, [30, 40, 50, 60])。
b. 因为 tuple 不支持对它的元素赋值,所以会抛出 TypeError 异常。
c. 以上两个都不是。
d. a 和 b 都是对的。
首先书中的代码 在Pycharm是运行不出来的。 在控制台就出现了这种神奇的情况
>>> 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
(1, 2, [3, 4, 5, 6])
所以答案是 d

谜题解释

示例 2-16 s[a] = b 背后的字节码
>>> dis.dis(‘s[a] += b’)
1 0 LOAD_NAME 0(s)
3 LOAD_NAME 1(a)
6 DUP_TOP_TWO
7 BINARY_SUBSCR 将 s[a] 的值存入 TOS(Top Of Stack,栈的顶端)
8 LOAD_NAME 2(b)
11 INPLACE_ADD 计算 TOS += b。这一步能够完成,是因为 TOS 指向的是一个可变对象(也就是[30,40])
12 ROT_THREE
13 STORE_SUBSCR s[a] = TOS 赋值。这一步失败,是因为 s 是不可变的元组(元组t)
14 LOAD_CONST 0(None)
17 RETURN_VALUE
所以呢 就完成了对列表的赋值 但是到 元组赋值处报错,但列表中的内容还是保存了

作者得到的教训,当然咱也应该得到

不要把可变对象放在元组里面

增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了
操作

查看 Python 的字节码并不难,而且它对我们了解代码背后的运行机制很有帮助

发布了32 篇原创文章 · 获赞 6 · 访问量 916

猜你喜欢

转载自blog.csdn.net/EEEEEEcho/article/details/103898677