Python切片详解

发布于 2021-12-16  30 次阅读


什么是切片操作

在Python中,切片(slice)是对序列型对象(如liststringtuple)的一种高级索引方法。普通索引只取出序列中一个下标对应的元素,而切片取出序列中一个范围对应的元素,这里的范围不是狭义上的连续片段。下面的代码初步展示了切片索引的力量。

 >>> a = list(range(10))
 >>> a
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[:5]
 [0, 1, 2, 3, 4]
 >>> a[5:]
 [5, 6, 7, 8, 9]
 >>> a[2:8]
 [2, 3, 4, 5, 6, 7]
 >>> a[::2]
 [0, 2, 4, 6, 8]
 >>> a[::-1]
 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

你可能还看不懂具体的语法(这正是本篇要介绍的),但你应该已经领略到了切片的花式操作。

写这篇文章的初衷是,一方面切片操作是Python中非常常见的,另一方面网上却很难找到全面系统的解析,比如以下结果是否让人有些迷惑:

 >>> a
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[0::-1]
 [0]
 >>> a[0:len(a):-1]
 []

为什么有len(a)和省略len(a)结果会不一样?

本文致力于真正讲清楚Python切片的使用方法。若您希望进一步了解切片的实现原理,以帮助我们自定义类的切片操作,敬请期待本文的续篇,《切片完全指南(原理篇)》


基本索引

我们从Python的基本索引开始,即单个整数的索引。假设被索引的序列仍为之前提到的a,则基本索引的语法为a[index],其中index为下标。读者可能会觉得这里过于简单,但我们要强调的是Python一个语法糖:负数下标索引,即:index可以取为负数,当其为-n时,对倒数第n个元素进行索引。我们用一张表格值观展示a的索引范围。

非负下标索引和负数下标索引共同构成了Python索引的有效范围:​。有效范围的概念对切片的理解非常重要,在基本索引中,索引超出有效范围时会抛出IndexError异常:

 >>> a
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[10]
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
 IndexError: list index out of range
 >>> a[-11]
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
 IndexError: list index out of range

但在切片中不是这样,我们将在下文具体解释。


简单切片

简单切片指的是这样的切片形式:a[start:stop],其行为是得到下标在这样一个前闭后开区间范围内的元素,其中startstop为负数时,简单看作是负数下标对应的位置即可:

 >>> a
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[2:3]
 [2]
 >>> a[5:9]
 [5, 6, 7, 8]
 >>> a[5:-1]
 [5, 6, 7, 8]
 >>> a[-5:9]
 [5, 6, 7, 8]
 >>> a[-5:-1]
 [5, 6, 7, 8]

事情到这里也很简单,下面着重讲解两个比较特殊的情况:超出有效索引范围缺省

超出有效索引范围

startstop超出上文提到的有效索引范围​时,切片操作不会抛出异常,而是进行截断。可以这样去理解截断机制:我们假象把索引范围扩充到全体整数,只不过小于​或大于​的区域对应空元素,在这个扩充后的数轴上进行切片,只需把最终结果中的所有空元素忽略即可。

来看几个具体的例子

 >>> a
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[-100:5]
 [0, 1, 2, 3, 4]
 >>> a[5:100]
 [5, 6, 7, 8, 9]
 >>> a[-100:100]
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[100:1000]
 []

另外,如果start的位置比stop还靠后怎么办?Python还是不会抛出异常,而是直接返回空序列:

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

缺省

startstop都是可以缺省的,在缺省的情况下,Python的行为是尽可能取最大区间,具体来说:

按照扩充索引范围的观点,start的缺省值是无穷小(​)stop的缺省值是无穷大(​)

 >>> a
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[:5]
 [0, 1, 2, 3, 4]
 >>> a[5:]
 [5, 6, 7, 8, 9]
 >>> a[100:]
 []

扩展切片

早期的Python解释器仅支持上述a[start:stop]形式的基本切片,后来加入了下面要介绍的切片形式,扩展切片的名称也流传下来,实际上不用担心,这早已是Python所支持的标准语法。

扩展切片指的是这样的切片形式:a[start:stop:step],其中step是一个非零整数,即比简单切片多了调整步长的功能,此时切片的行为可概括为:从start对应的位置出发,以step为步长索引序列,直至越过stop对应的位置,且不包括stop本身。事实上,简单切片就是step=1的扩展切片的特殊情况。需要详细解释的是step分别为正数和负数的两种情况。

step为正数

step为正数时,切片行为很容易理解,startstop截断缺省规则也与简单切片完全一致:

 >>> a
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[0:6:2]
 [0, 2, 4]
 >>> a[::2]
 [0, 2, 4, 6, 8]
 >>> a[:-2:2]
 [0, 2, 4, 6]
 >>> a[4::2]
 [4, 6, 8]

step为负数

step为负数时,切片将其解释为从start出发以步长|step|逆序索引序列,此时,startstop截断依然遵循前述规则,但缺省发生一点变化,因为我们说过,在缺省的情况下,Python的行为是尽可能取最大区间,此时访问是逆序的,start应尽量取大,stop应尽量取小,才能保证区间最大,因此:

按照扩充索引范围的观点,start的缺省值是无穷大(​)stop的缺省值是无穷小(​)

 >>> a
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 >>> a[5::-1]
 [5, 4, 3, 2, 1, 0]
 >>> a[:4:-2]
 [9, 7, 5]
 >>> a[::-1]
 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

总结

本文着重介绍了Python中序列类型对象的切片操作语法,通过引入扩充索引范围解释了切片操作的截断缺省两个较难理解的行为。


为了艾泽拉斯!