《流畅的Python》读书笔记3(第二章:序列构成的数组)

155 阅读4分钟

2.4 切片

在Python里,像列表、元组和字符串这类序列类型都支持切片操作,但是实际上切片操作比人们想象的要强大很多。

2.4.1 为什么切片和区间会忽略最后一个元素

在切片和区间操作里不包含区间范围的最后一个元素是Python的风格,这样做带来的好处如下:

  • 当只有最后一个位置信息时,我们可以快速看出切片和区间里有几个元素:range(3)和my_list[:3]都返回三个元素。
  • 当起止位置信息都有时,我们可以快速计算出切片和区间的长度,用最后一个元素减去第一个下标(stop-start)即可。
  • 这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两部分,只要协程my_list[:x]和my_list[x:]就可以了。

2.4.2 对对象进行切片

我们可以用s[a:b:c]的形式对s在a和b之间以c为间隔取值。c的值可以为负,负值意味着反向取值。

>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'

可以给切片命名:

>>> invoice ='1989  Pimoroni PiBrella $17.5 3 $52.50'
>>> SKU= slice(0,4)
>>> DESCRIPTION = slice(6,23)
>>> invoice[SKU]
'1989'
>>> invoice[DESCRIPTION]
'Pimoroni PiBrella'

2.4.3 给切片赋值

如果把切片放在赋值语句的左边,或者把它作为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会新建一个包含同样类型数据的序列来作为拼接的结果。

如果想把一个序列复制几份然后再拼接起来,更快捷的做法是把这个序列乘以一个整数。同样,这个操作会产生一个新序列:

>>> L = [1, 2, 3]
>>> L * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 5 * 'abcd'
'abcdabcdabcdabcdabcd'

建立有列表组成的列表

有时我们会需要初始化一个嵌套着几个列表的列表,最好的选择是使用列表推导式

示例2-12

>>> board = [['_']*3 for i in range(3)]
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X'
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

上面的方法等同于:

>>> board = []
>>> for i in range(3):
...     row = ['_']*3
...     board.append(row)
...
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X'
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

示例2-13 指向同一列表

>>> weird_board = [['_'] *  3] * 3
>>> weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][2] = 0
>>> weird_board
[['_', '_', 0], ['_', '_', 0], ['_', '_', 0]]

这里可能有人会有疑问,为什么['_'] * 3 不是同一个引用呢? 这是因为这里的列表元素'_'是字符串

2.6 序列的增量赋值

增量赋值运算符+=和*=的表现取决于它们的第一个操作对象。

+=背后的特殊方法是__idd__(就地加法)。但是如果一个类没有实现这个方法的话,Python就会退一步调用__add__

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

>>> L = [1,2,3]
>>> id(L)
1419957809352
>>> L *= 2
>>> L
[1, 2, 3, 1, 2, 3]
>>> id(L)
1419957809352
>>> t = (1,2,3)
>>> id(t)
1419964185120
>>> t *= 2
>>> t
(1, 2, 3, 1, 2, 3)
>>> id(t)
1419958022056

一个关于+=的谜题

>>> t = (1,2,[30,40])
>>> id(t)
1419964185120
>>> t[2] += [50,60]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
>>> id(t)
1419964185120

下面介绍一个非常有用的一个工具,可以可视化Python运行的过程:Python Tutor(pythontutor.com/)

image.png

image.png

2.7 list.sort和内置函数sorted

list.sort方法就地排序列表,sorted会新建一个列表作为返回值。这个方法可以接受任何形式的可迭代对象作为参数。

list.sort和sorted都有两个可选的关键字参数。

reverse

如果被设定为True,被排序的序列里的元素会以降序输出。默认值是False

key

一个只有一个参数的函数,这个函数会被用在序列里的每一个元素上,所产生的结果将是排序算法依赖的对比关键字。

>>> fruits = ['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits)
['apple', 'banana', 'grape', 'raspberry']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits, reverse=True)
['raspberry', 'grape', 'banana', 'apple']
>>> sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry']
>>> sorted(fruits, reverse=True, key=len)
['raspberry', 'banana', 'grape', 'apple']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> fruits.sort()
>>> fruits
['apple', 'banana', 'grape', 'raspberry']