编写高质量Python(第12条) 不要在切片中同时指定上标和下标

第12条 不要在切片中同时指定上标和下标

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

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

>>>
['red', 'yellow', 'blue']
['orange', 'green', 'purple']

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

x = b'mongoose'
y = x[::-1]
print(y)

>>>
b'esoognom'

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

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

>>>
司寿

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

w = '寿司'
x = w.encode('utf-8')
y = x[::-1]
z = y.decode('utf-8')

>>>
Traceback ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb8 in position 0: invalid start byte

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

x = [str(chr(i + ord('a'))) for i in range(8)]
print(x[::2])
print(x[::-2])

>>>
['a', 'c', 'e', 'g']
['h', 'f', 'd', 'b']

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

​ 那么 2::2 是什么意思?-2::-2、-2:2:-2、2:2:-2又是什么意思?

x[2::2]    # ['c', 'e', 'g']
x[-2::-2]  # ['g', 'e', 'c', 'a']
x[-2:2:-2] # ['g', 'e']
x[2:2:-2]  # []

​ 同时使用起止下标与步进切片会难懂。方括号里面写三个值显得太过于拥挤,读起来不太容易,而且指定了步进值(尤其是负数步进值)的时候。我们必须考虑:步进值为负数的时候,会从起始下标开始,倒着选取,一直选取到终止下标所在的位置,但不包含该位置本身。

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

y = x[::2]  # ['a', 'c', 'e', 'g']
z = y[1:-1] # ['c', 'e']

​ 像刚才那样先隔位选取再进行切割,会让程序多做一次浅拷贝。所以应该把最能缩减列表长度的那个切片操作放在前面。如果程序实在没有那么多时间或内存去分两步操作,那么可以改用内置的 itertools 模块的 islice 方法(参见 第36条),这个方法用起来更清晰,因为它的起止位置与步进值都不能是负数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值