Python 3.8从零开始学
上QQ阅读APP看书,第一时间看更新

3.1.2 分片

序列的索引用来对单个元素进行访问,但若需要对一个范围内的元素进行访问,使用序列的索引进行操作就相对麻烦了,这时我们就需要有一个可以快速访问指定范围元素的索引实现。

Python中提供了分片的实现方式,所谓分片,就是通过冒号相隔的两个索引下标指定索引范围。

比如“序列号”大巴上的同学被分成了6组,若把所有同学的序号放在一个字符串中,若想要取得第二组所有同学的序号,根据前面的做法,就需要从头开始一个一个下标地去取,这样做起来不但麻烦,也耗时。若使用分片的方式,则可以快速获取所有同学的序号。

把所有同学的序号放在一个字符串中,各个序号使用逗号分隔,现要取得第二组所有同学的序号并打印出来。在交互模式下输入:

>>> student='0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23, 24,25, 26,27,28,29,30'
>>> student[10:19]      #取得第二组所有同学的序号,加上逗号分隔符,需要取得10个字符
'5,6,7,8,9'
>>> student[-17:-1]     #负数表明从右开始计数,取得最后一组所有6名同学的序号
'25,26,27,28,29,3'

由操作结果可以看到,分片操作既支持正数索引,也支持负数索引,并且对于从序列中获取指定部分元素非常方便。

分片操作的实现需要提供两个索引作为边界,第一个索引下标所指的元素会被包含在分片内,第二个索引下标的元素不被包含在分片内。这个操作有点像数学里的a≤x<b,x是我们需要得到的元素,a是分片操作中的第一个索引下标,b是第二个索引下标,b不包含在x的取值范围内。

接着上面的示例,假设需要得到最后一组所有6名同学的序号,使用正数索引可以这样操作:

>>> student='0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23, 24,25,26,27,28,29,30'
>>> student[66:83]  #取得最后一组所有6名同学的序号
'25,26,27,28,29,30'

由输出结果可以看到,很方便地得到了最后一组所有6名同学的序号。

观察得到的结果,使用正数索引得到的最后一组所有6名同学的序号和使用负数索引得到最后一组所有6名同学的序号有一些差异,使用正数索引得到的结果中,最后的两个字符是30,而使用负数索引得到的结果中,最后一个字符是3,没有30这个字符串存在。为什么结果会不一致?我们观察结果得知,是使用负数索引的结果不对。

使用负数索引得到的结果没有输出最后一个元素。我们尝试使用索引下标0作为最后一个元素的下一个元素,输入如下:

>>> student[-17:0]
''

结果没有输出最后一个元素。再试试使用索引0作为最后一个元素的下一个元素,输入如下:

>>> number[-3: 0]
[]

输出结果有点奇怪,返回的是一个空字符串。这是为什么?

在Python中,只要在分片中最左边的索引下标对应的元素比它右边的索引下标对应的元素晚出现在序列中,分片结果返回的就会是一个空序列。比如在上面的示例中,索引下标-17代表字符串序列中倒数第17个元素,而索引下标0代表第1个元素,倒数第17个元素比第1个元素晚出现,即排在第1个元素后面,所以得到的结果是空序列。

那怎么通过负数索引的方式取得最后一个元素呢?

Python提供了一条捷径,使用负数分片时,若要使得到的分片结果包括序列结尾的元素,只需将第二个索引值设置为空即可。在交互模式下输入:

>>> student[-17:]  #取得最后一组所有6名同学的序号
'25,26,27,28,29,30'

由输出结果看到,此时使用负数索引得到的结果和使用正数索引的结果已经一致了。

正数索引是否可以将第2个索引值设置为空呢,会得到怎样的结果?在交互模式下输入:

>>> student[66:]  #取得最后一组所有6名同学的序号
'25,26,27,28,29,30'

由输出结果可以看到,正数索引也可以将第2个索引值设置为空,结果是会取得第1个索引下标之后的所有元素。

如果将分片中的两个索引值都设置为空,所得的结果又是怎样的呢?在交互模式下输入:

>>> student[:]  #取得整个数组
'0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23, 24,25,26,27,28,29,30'

由输出结果可以看到,将分片中的两个索引都设置为空,得到的结果是整个序列值,这种操作其实等价于直接打印出该变量。

进行分片时,分片的开始和结束点都需要指定(无论是直接还是间接),用这种方式取连续的元素没有问题,但若要取序列中不连续的元素就比较麻烦,或者直接不能操作。

比如要取一个整数序列中的所有奇数,以一个序列的形式展示出来,用前面当前所学的方法就不能实现了。

这里我们先引入列表的概念,首先介绍创建列表,关于列表的更多内容会在下一节中展开介绍。

创建列表和创建普通变量一样,用一对方括号括起来就创建了一个列表,列表里面可以存放数据或字符串,数据或字符串之间用逗号隔开,逗号隔开的各个对象就是列表的元素,列表中的元素下标从0开始。以下示例就是创建了一个列表:

>>> number[0: 10: 1]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

由上面的示例可以看到,分片包含另一个数字。这种方式就是步长的显式设置。看起来和隐式设置步长没什么区别,得到的结果也和之前一样。但若将步长设置为比1大的数,结果会怎样呢?请看以下示例:

>>> student=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28, 29,30]

对student列表做如下操作,在交互模式下输入:

>>> student
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
>>> student[0]
0
>>> student[1:4]
[1,2, 3]
>>> student[-3:-1]
[28, 29]
>>> student[-3:]
[28, 29, 30]
>>> student[:]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]

接下来我们看看如何从student中取得所有的奇数。

对于上面描述的情况,Python为我们提供了另一个参数——步长(step length),该参数通常是隐式设置的。在普通分片中,步长默认是1。分片操作就是按照这个步长逐个遍历序列中的元素,遍历后返回开始和结束点之间的所有元素。也可以理解为默认步长是1,在交互模式下输入:

>>> student[0:10:1]
[0,1, 2, 3, 4, 5, 6, 7, 8, 9]

由输出结果可以看到,分片包含另一个数字。这种方式就是步长的显式设置。将步长设置为1时得到的结果和不设置步长时得到的结果是一致的。但若将步长设置为比1大的数,得到的结果会怎样呢?交互模式中输入:

>>> student[0:10:2]
[0, 2, 4, 6, 8]

由输出结果可以看到,将步长设置为2时,所得到的是偶数序列,若想要得到奇数序列该怎么办呢?在交互模式下尝试如下:

>>> student[1:10:2]
[1, 3, 5, 7, 9]

由输出结果可以看到,所得到的结果就是我们前面想要的奇数序列。

步长设置为大于1的数时,会得到一个跳过某些元素的序列。例如,我们上面设置的步长为2,得到的结果序列是从开始到结束,每个元素之间隔1个元素的结果序列。还可以这样使用:

>>> student[:10:3]
[0, 3, 6, 9]
>>> student[2:6:3]
[2,5 ]
>>> student[2:5:3]
[2]
>>> student[1:5:3]
[1, 4]

由输出结果可以看到,步长的使用方式是非常很灵活的。可以根据自己的需要,非常便利地从列表序列中得到自己想要的结果序列。

除了上面的使用方式,还可以设置前面两个索引为空。操作如下:

>>> student[::3]
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

上面的操作将序列中每3个元素的第1个提取出来,前面两个索引都设置为空。如果将步长设置为0,会得到什么结果呢?在交互模式下输入:

>>> student[::0]
Traceback (most recent call last):
File "<pyshell#79>", line 1, in <module>
student[::0]
ValueError: slice step cannot be zero

由输出结果可以看到,程序执行出错,错误原因是步长不能为0。

既然步长不能为0,那步长是否可以为负数呢?请看下面的例子:

>>> student[10:0:-2]
[10, 8, 6, 4, 2]
>>> student[0:10:-2]
[]
>>> student[::-2]
[30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0]
>>> student[5::-2]
[5, 3, 1]
>>> student[:5:-2]
[30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6]
>>> student[::-1]
[30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> student[10:0:-1]          #第二个索引为0,取不到序列中的第一个元素
[10, 9, 8, 7, 6, 5, 4, 3, 2,1]
>>> student[10::-1]         #设置第二个索引为空,可以取到序列的第一个元素
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1,0]
>>> student[2::-1]         #设置第二个索引为空,可以取到序列的第一个元素
[2, 1, 0]
>>> student[2:0:-1]         #第二个索引为0,取不到序列中的第一个元素
[2,1]

查看上面的输出结果,使用负数步长时的结果跟使用正数步长的结果是相反的。

这就是Python中正数步长和负数步长的不同之处。对于正数步长,Python会从序列的头部开始从左向右提取元素,直到序列中的最后一个元素;而对于负数步长,则是从序列的尾部开始从右向左提取元素,直到序列的第一个元素。正数步长必须让开始点小于结束点,否则得到的结果序列是空的;而负数步长必须让开始点大于结束点,否则得到的结果序列也是空的。

提示


使用负数步长时,要取得序列的第一个元素,即索引下标为0的元素,需要设置第二个索引为空。