![Effective Python:编写高质量Python代码的90个有效方法(原书第2版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/417/39980417/b_39980417.jpg)
第12条 不要在切片里同时指定起止下标与步进
除了基本的切片写法(参见第11条)外,Python还有一种特殊的步进切片形式,也就是somelist[start:end:stride]
。这种形式会在每n
个元素里面选取一个,这样很容易就能把奇数位置上的元素与偶数位置上的元素分别通过x[::2]
与x[1::2]
选取出来[1]。
![058-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/058-01.jpg?sign=1738813267-N4V7jEXxK6pEiVJkBKN3FsO3MVngEIVh-0-c3fcbb77e5e1928dd85587e1249ba3e6)
带有步进的切片经常会引发意外的效果,并且使程序出现bug。例如,Python里面有个常见的技巧,就是把-1当成步进值对bytes
类型的字符串做切片,这样就能将字符串反转过来。
![059-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/059-01.jpg?sign=1738813267-80MPvV0JTAbpfp7i18jkFIea4G45Q2Ex-0-83ff7a64170594ddb81630e48f711ef9)
Unicode形式的字符串也可以这样反转(参见第3条)。
![059-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/059-02.jpg?sign=1738813267-9Zq8i5ML5lBPxVQRK4zYyzcbenyuACoP-0-57f71e7d09118c3604f6397893b75fe9)
但如果把这种字符串编码成UTF-8标准的字节数据,就不能用这个技巧来反转了。
![059-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/059-03.jpg?sign=1738813267-Jp1wTJGazkpBYxxR3HkaJbwLyKXqbFm1-0-f1aea21613dc17f19bf27c6afcbaee2f)
除了-1之外,用其他负数做步进值,有没有意义呢?请看下面的例子:
![059-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/059-04.jpg?sign=1738813267-kUGxVP0zIeRbNKLcHeLMisKHmrM8fcmF-0-847890c29cf0ff1457f2be21eb47460b)
上例中,::2
表示从头开始往后选,每两个元素里面选一个。::-2
的含义稍微有点儿绕,表示从末尾开始往前选,每两个元素里面选一个。
2::2
是什么意思?-2::-2
、-2:2:-2
、2:2:-2
又是什么意思?[2]
![059-05](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/059-05.jpg?sign=1738813267-6RCqa1rtFwccXJzgxhyZN4j9mkUlG1ls-0-cfcab88186293a3cdf845a295735f15b)
同时使用起止下标与步进会让切片很难懂。方括号里面写三个值显得太过拥挤,读起来不大容易,而且在指定了步进值(尤其是负数步进值)的时候,我们必须很仔细地考虑:这究竟是从前往后取,还是从后往前取[3]?
为了避免这个问题,笔者建议大家不要把起止下标和步进值同时写在切片里。如果必须指定步进,那么尽量采用正数,而且要把起止下标都留空。即便必须同时使用步进值与起止下标,也应该考虑分成两次来写。
![060-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/060-01.jpg?sign=1738813267-On7DnNaZ3VfgpAgvTO0hjw43LdTjhvUM-0-3298c3077b8566ef24a7e820191d3e5c)
像刚才那样先隔位选取然后再切割,会让程序多做一次浅拷贝(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]步进值是负数的时候,会从起始下标开始,倒着选取,一直选到终止下标所在的位置,但不包含该位置本身。——译者注