Python 序列构成的数组(切片)

切片

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

这一节主要讨论的是这些高级切片形式的用法,它们的实现方法则会在
第 10 章的一个自定义类里提到。这么做主要是为了符合这本书的哲
学:先讲用法,第四部分中再来讲如何创建新类。

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

在切片和区间操作里不包含区间范围的最后一个元素是 Python 的风格,
这个习惯符合 Python、C 和其他语言里以 0 作为起始下标的传统。这样
做带来的好处如下。

当只有最后一个位置信息时,我们也可以快速看出切片和区间里有
几个元素:range(3) 和 my_list[:3] 都返回 3 个元素。
当起止位置信息都可见时,我们可以快速计算出切片和区间的长
度,用后一个数减去第一个下标(stop - start)即可。
这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两
部分,只要写成 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 的值还可以为负,负值意味着反向取值。下面
的 3 个例子更直观些:

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

另一个例子是在第 1 章中用 deck[12::13] 的形式在未洗过的牌里把每
种花色的 A 拿出来:

>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'),
Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]

a🅱️c 这种用法只能作为索引或者下标用在 [] 中来返回一个切片对
象:slice(a, b, c)。在 10.4.1 节中会讲到,对
seq[start:stop:step] 进行求值的时候,Python 会调用
seq.getitem(slice(start, stop, step))。就算你还不会自
定义序列类型,了解一下切片对象也是有好处的。例如你可以给切片命
名,就像电子表格软件里给单元格区域取名字一样。

比如,要解析示例 2-11 中所示的纯文本文件,这时使用有名字的切片
比用硬编码的数字区间要方便得多,注意示例里的 for 循环的可读性有
多强。

示例 2-11 纯文本文件形式的收据以一行字符串的形式被解析

>>> invoice = """
... 0.....6................................40........52...55........
... 1909 Pimoroni PiBrella $17.50 3 $52.50
... 1489 6mm Tactile Switch x20 $4.95 2 $9.90
... 1510 Panavise Jr. - PV-201 $28.00 1 $28.00
... 1601 PiTFT Mini Kit 320x240 $34.95 1 $34.95
... """
>>> SKU = slice(0, 6)
>>> DESCRIPTION = slice(6, 40)
>>> UNIT_PRICE = slice(40, 52)
>>> QUANTITY = slice(52, 55)
>>> ITEM_TOTAL = slice(55, None)
>>> line_items = invoice.split('\n')[2:]
>>> for item in line_items:
... print(item[UNIT_PRICE], item[DESCRIPTION])
...
$17.50 Pimoroni PiBrella
$4.95 6mm Tactile Switch x20
$28.00 Panavise Jr. - PV-201
$34.95 PiTFT Mini Kit 320x240

在 10.4 节还有更多机会来了解切片(slice)对象。如果从 Python 用户
的角度出发,切片还有个两个额外的功能:多维切片和省略表示法
(…)。

多维切片和省略

[] 运算符里还可以使用以逗号分开的多个索引或者是切片,外部库
NumPy 里就用到了这个特性,二维的 numpy.ndarray 就可以用 a[i,
j] 这种形式来获取,抑或是用 a[m:n, k:l] 的方式来得到二维切片。
稍后的示例 2-22 会展示这个用法。要正确处理这种 [] 运算符的话,对
象的特殊方法 getitemsetitem 需要以元组的形式来接收
a[i, j] 中的索引。也就是说,如果要得到 a[i, j] 的值,Python 会
调用 a.getitem((i, j))。

Python 内置的序列类型都是一维的,因此它们只支持单一的索引,成对
出现的索引是没有用的。

省略(ellipsis)的正确书写方法是三个英语句号(…),而不是
Unicdoe 码位 U+2026 表示的半个省略号(…)。省略在 Python 解析器眼
里是一个符号,而实际上它是 Ellipsis 对象的别名,而 Ellipsis 对象又是 ellipsis 类的单一实例。 它可以当作切片规范的一部分,也可
以用在函数的参数清单中,比如 f(a, …, z),或 a[i:…]。在
NumPy 中,… 用作多维数组切片的快捷方式。如果 x 是四维数组,那
么 x[i, …] 就是 x[i, :, :, :] 的缩写。如果想了解更多,请参
见“Tentative NumPy
Tutorial”(http://wiki.scipy.org/Tentative_NumPy_Tutorial)。

在写这本书的时候,我还没有发现在 Python 的标准库里有任何
Ellipsis 或者是多维索引的用法。如果你知道,请告诉我。这些句法
上的特性主要是为了支持用户自定义类或者扩展,比如 NumPy 就是个
例子。

除了用来提取序列里的内容,切片还可以用来就地修改可变序列,也就
是说修改的时候不需要重新组建序列。

给切片赋值

如果把切片放在赋值语句的左边,或把它作为 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]

➊ 如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代
对象。即便只有单独一个值,也要把它转换成可迭代的序列。
序列的拼接操作可谓是众所周知,任何一本 Python 入门教材都会介绍 +
和 * 的用法,但是在这些用法的背后还有一些可能被忽视的细节。下面
就来看看这两种操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

钢铁男儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值