Effective Python(6): 单次切片操作,不要同时
2019-09-29 本文已影响0人
warmsirius
一、stride
参数
除了基本的切片操作(参考Effective Python(5): 了解切割序列的方法), Python还提供了somelist[start:end:stride]
形式的写法,以实现步进式切割。
例如:
a = ["red", "orange", "yellow", "green", "blue", "purple"]
odds = a[::2]
evens = a[1::2]
print(odds)
print(evens)
# Results
['red', 'yellow', 'blue']
['orange', 'green', 'purple']
二、问题
问题在于,采用stride
方式进行切片时,经常会出现不符合预期的结果。
1. Python中有一个技巧,利用-1做步进值,可以将以字节形式存储的字符串反转过来
>>> x = b'diagnosor'
>>> y = x[::-1]
>>> y
b'rosongaid'
但是这种技巧对字节串和
ASCII
字符有用,对已经编码成UTF8
字节串的Unicode
字符来说,则无法奏效。
>>> w = '谢谢'
>>> x = w.encode('utf8')
>>> x
b'\xe8\xb0\xa2\xe8\xb0\xa2'
>>> y = x[::-1]
>>> y
b'\xa2\xb0\xe8\xa2\xb0\xe8'
>>> y.decode('utf8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa2 in position 0: invalid start byte
2. 步长举例
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
a[::2] # ['a', 'c', 'e', 'g']
a[::-2] # ['h', 'f', 'd', 'b']
a[2::2] # ['c', 'e', 'g']
a[-2::-2] # ['g', 'e', 'c', 'a']
a[-2:2:-2] # ['g', 'e']
a[2:2:-2] # []
通过上面例子可以看出:切割列表时,如果指定了stride
,那么代码可能会变得相当费解。在一对括号中写上3个数字显得太过拥挤,导致代码难以阅读。
解决:
- a. 不把
start
、end
、stride
写在一起。 - b. 如果非要用
stride
尽量采用正值,同时省略start
和end
索引。 - c. 如果一定要配合
start
或end索引来使用stride,先做步进式切片,把切割结果赋给某个变量,然后在那个变量上面做第二次切割。
b = a[::2]
c = b[1:-1]
这种先做步进切割,再做范围切割的办法,会多产生一份原数据的浅拷贝。执行第一次切割操作时,应该尽量缩减切割后的列表尺寸。
注意:
如果开发程序对执行时间或内存用量的要求非常严格,以致不能采用两阶段切割法,那就考虑Python内置的itertools
模块中的islice
方法,这个方法不允许为start
、end
、stride
指定负值。
三、要点
- 尽量使用
stride
为整数,且不带start
和end
索引的切割操作。 - 避免用负数做
stride
- 尽量不要在一个列表括号中同时使用
start
、end
、和stride
,如果确实需要执行这种操作,那就考虑将其拆解为两条赋值语句,其中一条做范围切割,另一条做步进切割,或考虑用内置的itertools
中islice
方法