Skip to content

高效Python90条之第12条 不要在切片里同时指定起止下标与步进

Python对切片的步进表达形式是somelist[start:end:stride]

x = ["red", "orange", "yellow", "green", "blue", "purple"]
odds = x[::2]
evens = x[1::2]
print(odds)  # 输出:['red', 'yellow', 'blue']
print(evens)  # 输出:['orange', 'green', 'purple']

下面是对bytes和Unicode类型的字符串做切片,将字符串反转。

x = b'mongoose'
y = x[::-1]
print(y)  # 输出:b'esoognom'

x = '寿司'
y = x[::-1]
print(y)  # 输出:'司寿'

但如果把这种字符串编码成UTF-8标准的字节数据,就不能直接通过x[::-1]方法来反转了,而是需要通过解码来实现。

w = "寿司"

# 无法直接反转
x = w.encode("utf-8")
y = x[::-1]
print(y)  # 输出:b'\xb8\x8f\xe5\xbf\xaf\xe5'
z = y.decode("utf-8") # UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb8 in position 0: invalid start byte

# 解码后再反转
x = w.encode("utf-8")
y = x.decode("utf-8")[::-1]
print(y)  # 输出:'司寿'

看下面一些负数做步进值的例子。步进值是负数的时候,会从起始下标开始,倒着选取,一直选到终止下标所在的位置,但不包含该位置本身。

x = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
# 从第一个元素开始,每隔一个元素选取一个元素。 
print(x[::2]) # 输出:['a', 'c', 'e', 'g']
# 从最后一个元素开始,每隔一个元素选取一个元素。
print(x[::-2]) # 输出:['h', 'f', 'd', 'b']   
# 从第三个元素开始,每隔一个元素选取一个元素。
print(x[2::2]) # 输出:['c', 'e', 'g']
# 从倒数第二个元素开始,每隔一个元素选取一个元素。
print(x[-2::-2]) # 输出:['g', 'e', 'c', 'a']
# 从倒数第二个元素开始,每隔一个元素选取一个元素,直到第三个元素。
print(x[-2:2:-2]) # 输出:['g', 'e']
# 从第三个元素开始,每隔一个元素选取一个元素,直到第三个元素。
print(x[2:2:-2]) # 输出:[]

各种起止位和正负步进值会让代码可读性下降,实践中的建议是,不要把起止下标和步进值同时写在切片里。如果必须指定步进,那么尽量采用正数,而且要把起止下标都留空。即便必须同时使用步进值与起止下标,也应该考虑分成两次来写。

下面是建议做法。

示例 1:避免同时指定起止下标和步进值

a = ["a", "b", "c", "d", "e", "f", "g", "h"]

# 不推荐写法:同时指定起止下标和步进值
subset = a[1:7:2]  # 从索引 1 到索引 7,每隔 2 个元素选取一个
print("Subset:", subset)  # 输出:Subset: ['b', 'd', 'f']

# 推荐写法:
# 先切片
subset = a[1:7]
# 再步进,直接省略起止下标,只用步进值
subset = subset[::2]
print("Subset:", subset)  # 输出:Subset: ['b', 'd', 'f']

示例 2:使用 itertools.islice

import itertools

a = ["a", "b", "c", "d", "e", "f", "g", "h"]

# 使用 itertools.islice,可以同时指定起始位置、终止位置和步进值,而不会影响代码的可读性。
subset = list(itertools.islice(a, 1, 7, 2))
print("Subset using itertools.islice:", subset)  # 输出:Subset using itertools.islice: ['b', 'd', 'f']

要点

  • 同时指定切片的起止下标与步进值理解起来会很困难。
  • 如果要指定步进值,那就省略起止下标,而且最好采用正数作为步进值,尽量别用负数。
  • 不要把起始位置、终止位置与步进值全都写在同一个切片操作里。如果必须同时使用这三项指标,那就分两次来做(其中一次隔位选取,另一次做切割)​,也可以改用itertools内置模块里的islice方法。