Effective Python:编写高质量Python代码的90个有效方法(原书第2版)
上QQ阅读APP看书,第一时间看更新

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

除了基本的切片写法(参见第11条)外,Python还有一种特殊的步进切片形式,也就是somelist[start:end:stride]。这种形式会在每n个元素里面选取一个,这样很容易就能把奇数位置上的元素与偶数位置上的元素分别通过x[::2]x[1::2]选取出来[1]

058-01

带有步进的切片经常会引发意外的效果,并且使程序出现bug。例如,Python里面有个常见的技巧,就是把-1当成步进值对bytes类型的字符串做切片,这样就能将字符串反转过来。

059-01

Unicode形式的字符串也可以这样反转(参见第3条)。

059-02

但如果把这种字符串编码成UTF-8标准的字节数据,就不能用这个技巧来反转了。

059-03

除了-1之外,用其他负数做步进值,有没有意义呢?请看下面的例子:

059-04

上例中,::2表示从头开始往后选,每两个元素里面选一个。::-2的含义稍微有点儿绕,表示从末尾开始往前选,每两个元素里面选一个。

2::2是什么意思?-2::-2-2:2:-22:2:-2又是什么意思?[2]

059-05

同时使用起止下标与步进会让切片很难懂。方括号里面写三个值显得太过拥挤,读起来不大容易,而且在指定了步进值(尤其是负数步进值)的时候,我们必须很仔细地考虑:这究竟是从前往后取,还是从后往前取[3]

为了避免这个问题,笔者建议大家不要把起止下标和步进值同时写在切片里。如果必须指定步进,那么尽量采用正数,而且要把起止下标都留空。即便必须同时使用步进值与起止下标,也应该考虑分成两次来写。

060-01

像刚才那样先隔位选取然后再切割,会让程序多做一次浅拷贝(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]步进值是负数的时候,会从起始下标开始,倒着选取,一直选到终止下标所在的位置,但不包含该位置本身。——译者注