《流畅的python》2.4切片 读书笔记
在 Python 里,像列表(list)、元组(tuple)和字符串(str)这类序列类型都支持切片操作,但是实际上切片操作比人们所想象的要强大很多。
一、为什么切片和区间会忽略最后一个元素
1.当只有最后一个位置信息时,我们也可以快速看出切片和区间里有几个元素:range(3) 和 my_list[:3] 都返回 3 个元素。
2…当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,用后一个数减去第一个下标(stop - start)即可。
3.这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两部分,只要写成my_list[:x] 和 my_list[x:] 就可以了
如:
>>> l = [10, 20, 30, 40, 50, 60]
>>> l[:2] # 在下标2的地方分割
[10, 20]
>>> l[2:]
[30, 40, 50, 60]
>>> l[:3] # 在下标3的地方分割
[10, 20, 30]
>>> l[3:]
[40, 50, 60]
二、对对象进行切片
用 s[a🅱️c] 的形式对 s 在 a 和 b 之间以 c 为间隔 取值。c 的值还可以为负,负值意味着反向取值。
>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]'elcycib'
>>> s[::-2]
'eccb'
三、多维切片和省略
[] 运算符里还可以使用以逗号分开的多个索引或者是切片,外部库 NumPy 里就用到了这个特性,二维的 numpy.ndarray 就可以用 a[i, j] 这种形式来获取,抑或是用 a[m:n, k:l] 的方式来得到二维切片。
要正确处理这种 [] 运算符的话,对象的特殊方法 getitem 和 setitem 需要以元组的形式来接收 a[i, j] 中的索引。也就是说,如果要得到 a[i, j] 的值,Python 会调用a.getitem((i, j))。
除了用来提取序列里的内容,切片还可以用来就地修改可变序列,也就是说修改的时候不 需要重新组建序列。
四、给切片赋值
如果把切片放在赋值语句的左边,或把它作为 del 操作的对象,我们就可以对序列进行嫁接、切除或就地修改操作。
>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]
如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象。即便只有单
独一个值,也要把它转换成可迭代的序列。
(2.5)五、对系列使用+和*
通常 + 号两侧的序列由相同类型的数据 所构成,在拼接的过程中,两个被操作的序列都不会被修改,Python 会新建一个包含同样类型数据的序列来作为拼接的结果。如果想要把一个序列复制几份然后再拼接起来,更快捷的做法是把这个序列乘以一个整数。
建立由列表组成的列表
有时我们会需要初始化一个嵌套着几个列表的列表,譬如一个列表可能需要用来存放不同 的学生名单,或者是一个井字游戏板 3 上的一行方块。想要达成这些目的,最好的选择是使用列表推导。
>>> board = [['_'] * 3 for i in range(3)]
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X'
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
建立一个包含 3 个列表的列表,被包含的 3 个列表各自有 3 个元素。打印出这个嵌套
列表。
把第 1 行第 2 列的元素标记为 X,再打印出这个列表。
(2.6)六、序列的增量赋值
+= 背后的特殊方法是 iadd (用于“就地加法”)。但是如果一个类没有实现这个方法的话,Python 会退一步调用 add 。
>>> l = [1, 2, 3]
>>> id(l)
4311953800 # 刚开始时列表的 ID。
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
4311953800 #运用增量乘法后,列表的 ID 没变,新元素追加到列表上。
>>> t = (1, 2, 3)
>>> id(t)
4312681568 #元组最开始的 ID。
>>> t *= 2
>>> id(t)
4301348296 #运用增量乘法后,新的元组被创建。
对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个新对象,而解释器 需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素。
一个谜题:
>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
到底会发生下面 4 种情况中的哪一种?
a. t 变成 (1, 2, [30, 40, 50, 60])。
b. 因为 tuple 不支持对它的元素赋值,所以会抛出 TypeError 异常。
c. 以上两个都不是。
d. a 和 b 都是对的。
经验教训:
1.不要把可变对象放在元组里面。
2.增量赋值不是一个原子操作。
3.查看 Python 的字节码并不难,而且它对我们了解代码背后的运行机制很有帮助。