《Effective Python》笔记-第2章 列表与字典
第十一条:学会对序列做切片
在Python中,凡是实现了_getitem_与_setitem_这两个特殊方法的类都可以切割。最基本的写法是somelist[start:end]这一形式来切割,也就是从start开始切割到end这个位置,但不包含end本身的元素。如果是从头开始切割,可以省略冒号左侧的下标0,这样看起来更加清晰。同样的,如果一直切割到末尾,也应省略冒号右侧的下标。
用负数作下标表示从列表末尾往前算。
1
2
3
4
5
6
7
8
9
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
a[:] # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
a[:5] # ['a', 'b', 'c', 'd', 'e']
a[:-1] # ['a', 'b', 'c', 'd', 'e', 'f', 'g']
a[4:] # ['e', 'f', 'g', 'h']
a[-3:] # ['f', 'g', 'h']
a[2:5] # ['c', 'd', 'e']
a[2:-1] # ['c', 'd', 'e', 'f', 'g']
a[-3:-1] # ['f', 'g']
切割时所用的下标可以越界,如果起点与终点所确定的范围超出了列表边界,系统会自动忽略不存在的元素。
把切片放在赋值符号左侧可以将原列表中这段范围内的元素赋值符号右侧的元素替换掉,但可能会改变原列表的长度。
第十二条:不要在切片里同时指定起止下标与步进
Python中还有一种特殊的切片写法,也就是somelist[start:end:stride]。这种形式会在每n个元素中选取一个,这就很容易把奇数和偶数位置上的元素通过x[::2]和x[1::2]分别选取出来。
带有步进的切片经常会引发意外效果,致使出现bug,例如Python中有个常见的技巧,就是把-1当成步进值对bytes类型的字符串做切片,这样就能把字符串反转过来。Unicode字符串也可以这样反转。
但如果把这种字符串编码成UTF-8的标准字节数据,就不能用这个技巧反转了。
同时使用起止下标和步进会让切片变得很难懂。方括号里写三个值也过于拥挤,不易读,而且在指定了步进值后(尤其是负数),我们必须仔细考虑:这究竟是从前往后还是从后往前取?
为了避免这个问题,不建议把起止下标和步进同时写在切片里。如果必须使用,尽量将步进值设置为正数,且把起止下标留空。否则应该考虑分两次做,也可以改用itertools内置模块里的islice方法。
第十三条:通过待星号的unpacking操作来捕获多个元素,不要用切片
基本的unpacking操作有一个限制,就是必须提前确定要拆解的序列的长度。例如要取有序序列中前两个元素:
1
2
3
4
5
6
7
cars = [2,5,7,8,4,0,5,6]
cars_descending = cars.sorted(cars, reverse=True)
first_car, second_car = cars_descending
>>>
Traceback ...
ValueError: too many values to unpack (expected 2)
以上问题虽然可以通过下标和切片来解决,但会让代码看起来很乱。这个问题通过带星号的表达式(starred expression)来解决会好一点,这也是一种unpacking操作,它可以把无法由普通变量接收的那些元素全部囊括进去。下面是代码,既不用做切片,也不需要使用下标:
1
first_car, second_car, *others = cars_descending
这种带星号的表达式可以出现在任意位置,所以他能捕获序列中任意一段元素。不过这种写法有一个限制,必须使用一个普通变量搭配。另外,对于单层结构来说,同一级内只能由一个带星号的unpacking。
第十四条:用sort方法的key参数来表示复杂的排序逻辑
凡是具备自然顺序的内置类型几乎都可以用sort方法排列,例如字符串、浮点数等。
对象的排序标准通常是针对对象中的某个属性。我们可以把这样的排序逻辑定义成函数,然后将这个函数传给sort方法的key参数。下面用lambda关键字定义这个函数,传给sort方法的key参数,让我们能够按照name的字母顺序排列这些Tool对象。
1
tools.sort(key=lambda x: x.name)
有时我们可能需要用多个标准来排序。在Python中,最简单的方案就是利用元组tuple类型实现。元组是一种不可变的序列,能够存放任意类型的Python值。可以把它们放到一个元组中,让key函数返回这样的元组。对于支持一元减操作符的类型来说,可以单独给这项指标取反,让排序算法在这项指标上按照相反的方向处理。
第十五条:不要过分依赖给字典添加条目时所用的顺序
从Python3.7版本开始,我们就可以确信迭代标准的字典时所看到的顺序跟这些键值对插入字典时的顺序一致。
在Python代码中,我们很容易就能定义跟标准的字典很像但本身并不是dict实例的对象(例如SortedDict类,它与标准的字典遵循同一套协议)。对于这种类型的对象,不能假设迭代时看到的顺序必定与插入时的顺序相同。如果要频繁插入或弹出键值对(例如要实现least-recently-used缓存),那么OrderedDict可能比标准的Python dict类型更合适。
如果不想把这种跟标准字典很相似的类型也当成标准类型处理,那么可以考虑三种办法:
- 不要依赖插入时的顺序编写代码。
- 在程序运行时明确判断它的顺序是不是标注的字典。
- 给代码添加类型注解并做静态分析。
第十六条:用get处理键不在字典中的情况,不要使用in与KeyError
有四种办法可以处理键不在字典中的情况:
- in表达式
- KeyError异常
- get方法
- setdefault方法
如果跟键相关联的值是像计数器这样的基本类型,那么get方法就是最好的方案;如果是那种构造起来开销比较大,或是容易出异常的类型,那么可以把这个方法与赋值表达式结合使用。
如果使用setdefault方法,每次都要构造一个新的默认值出来,这有可能产生较大的性能开销。
即使看上去最应该使用setdefault方案,也不一定真的要用setdefault方案,而是可以考虑用defaultdict取代普通的dict。
第十七条:用defaultdict处理内部状态中缺失的元素,而不要用setdefault
如果你管理的字典可能需要添加任意的键,那么应该考虑能否用内置的collections模块中的defaultdict实例来解决问题。它会在键缺失的情况下,自动添加这个键以及键所对应的默认值。
如果这种键名比较随意的字典是别人传给你的,你无法把它创建成defaultdict,那么应该考虑通过get方法访问其中的键值。然而,在个别情况下,也可以考虑改用setdefault方法,因为那样写更短。
第十八条:学会利用_missing_构造依赖键的默认值
有一些任务是setdefault方法和defaultdict类型都处理不好的。如果创建默认值需要较大的开销,或者可能抛出异常,那就不是和用dict类型的setdefault方法实现。如果要构造的默认值必须根据键名来确定,可以使用Python内置的一种解决方案,可以通过继承dict类型并实现__missing__特殊方法来解决这个问题。