2022年 11月 9日

Python基础知识:切片

什么是切片操作

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

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

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

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

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

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

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


基本索引

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

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

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

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


简单切片

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

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

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

超出有效索引范围

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

来看几个具体的例子

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

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

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

缺省

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

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

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


扩展切片

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

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

step为正数

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

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

step为负数

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

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

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