第12条 不要在切片里同时指定起止下标与步进
除了基本的切片写法(参见第11条)外,Python还有一种特殊的步进切片形式,也就是somelist[start:end:stride]
。这种形式会在每n
个元素里面选取一个,这样很容易就能把奇数位置上的元素与偶数位置上的元素分别通过x[::2]
与x[1::2]
选取出来[1]。
带有步进的切片经常会引发意外的效果,并且使程序出现bug。例如,Python里面有个常见的技巧,就是把-1当成步进值对bytes
类型的字符串做切片,这样就能将字符串反转过来。
Unicode形式的字符串也可以这样反转(参见第3条)。
但如果把这种字符串编码成UTF-8标准的字节数据,就不能用这个技巧来反转了。
除了-1之外,用其他负数做步进值,有没有意义呢?请看下面的例子:
上例中,::2
表示从头开始往后选,每两个元素里面选一个。::-2
的含义稍微有点儿绕,表示从末尾开始往前选,每两个元素里面选一个。
2::2
是什么意思?-2::-2
、-2:2:-2
、2:2:-2
又是什么意思?[2]
同时使用起止下标与步进会让切片很难懂。方括号里面写三个值显得太过拥挤,读起来不大容易,而且在指定了步进值(尤其是负数步进值)的时候,我们必须很仔细地考虑:这究竟是从前往后取,还是从后往前取[3]?
为了避免这个问题,笔者建议大家不要把起止下标和步进值同时写在切片里。如果必须指定步进,那么尽量采用正数,而且要把起止下标都留空。即便必须同时使用步进值与起止下标,也应该考虑分成两次来写。
像刚才那样先隔位选取然后再切割,会让程序多做一次浅拷贝(shallow copy)。所以,应该把最能缩减列表长度的那个切片操作放在前面。如果程序实在没有那么多时间或内存去分两步操作,那么可以改用内置的itertools
模块中的islice
方法(参见第36条),这个方法用起来更清晰,因为它的起止位置与步进值都不能是负数。
要点
- 同时指定切片的起止下标与步进值理解起来会很困难。
- 如果要指定步进值,那就省略起止下标,而且最好采用正数作为步进值,尽量别用负数。
- 不要把起始位置、终止位置与步进值全都写在同一个切片操作里。如果必须同时使用这三项指标,那就分两次来做(其中一次隔位选取,另一次做切割),也可以改用
itertools
内置模块里的islice
方法。
[1]作者这里说的奇数与偶数位置,是口语里的意思,从1开始算,而列表的下标,则是从0开始算,所以奇数位置上的元素,例如第一个('red'
)、第三个('yellow'
)、第五个('blue'
),在列表里面是第“0”个(x[0]
)、第“2”个(x[2]
)、第“4”个(x[4]
)。——译者注
[2]详细规则参见https://docs.python.org/3/library/stdtypes.html#common-sequence-operations。——译者注
[3]步进值是负数的时候,会从起始下标开始,倒着选取,一直选到终止下标所在的位置,但不包含该位置本身。——译者注