1. 学会对序列做切片
凡是实现了__getitem__, __setitem__两个特殊方法的类都可以切割,比如内置的list/str/bytes, 最基本的方式是somelist[start:end]不包含end本身,区间为前开后闭区间
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
print("Middle two: ", a[3:5]) # Middle two: ['d', 'e']
print("All but ends: ", a[1:7]) # All but ends: ['b', 'c', 'd', 'e', 'f', 'g']
# 如果从头开始切割列表 可以省略左侧下标
assert a[:5] == a[0:5]
# 如果一直取到列表末尾 可以省略右侧下标
assert a[5:] == a[5:len(a)]
# 可以使用负数作为下标从尾部往前算
print(a[:-1]) # ['a', 'b', 'c', 'd', 'e', 'f', 'g']
print(a[-3:]) # ['f', 'g', 'h']
# 切片在左侧可以赋值
b = [100,101]
print(b[:]) # [100, 101]
b[:] = [101,102,103]
print(b[:]) # [101, 102, 103]
- 切片要尽可能写得简单一些:如果从头开始选取,就省略起始下标0;如果选到序列末尾,就省略终止下标
- 切片允许起始下标或终止下标越界,所以很容易就能表达“取开头多少个元素”(例如a[:20])或“取末尾多少个元素”(例如a[-20:0])等含义,而不用担心切片是否真有这么多元素
- 把切片放在赋值符号的左侧可以将原列表中这段范围内的元素用赋值符号右侧的元素替换掉,但可能会改变原列表的长度
2. 不要在切片里同时指定起止下标与步进
同时使用起止下标与步进会让切片很难懂。方括号里面写三个值显得太过拥挤,读起来不大容易,而且在指定了步进值(尤其是负数步进值)的时候,我们必须很仔细地考虑:这究竟是从前往后取,还是从后往前取[3]?为了避免这个问题,笔者建议大家不要把起止下标和步进值同时写在切片里。如果必须指定步进,那么尽量采用正数,而且要把起止下标都留空。即便必须同时使用步进值与起止下标,也应该考虑分成两次来写
x = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
# 这种方式会让程序多做一次浅拷贝
print(y := x[::2]) # ['a', 'c', 'e', 'g']
print(z := y[1:-1]) # ['c', 'e']
# 可以通过itertools操作, 防止浅拷贝
# 参考第36条
- 同时指定切片的起止下标与步进值理解起来会很困难。如果要指定步进值,那就省略起止下标,而且最好采用正数作为步进值,尽量别用负数
- 不要把起始位置、终止位置与步进值全都写在同一个切片操作里。如果必须同时使用这三项指标,那就分两次来做(其中一次隔位选取,另一次做切割),也可以改用itertools内置模块里的islice方法
3. 通过带星号的unpacking操作来捕获多个元素,不要用切片
通过下标和切片会让代码看起来很乱。而且,用这种办法把序列中的元素分成多个子集合,其实很容易出错,因为我们通常容易把下标多写或少写一个位置。例如,若修改了其中一行,但却忘了更新另一行,那就会遇到这种错误。 这个问题通过带星号的表达式(starred expression)来解决会更好一些,这也是一种unpacking操作,它可以把无法由普通变量接收的那些元素全都囊括进去。下面用带星号的unpacking操作改写刚才那段代码,这次既不用取下标,也不用做切片
cat_ages = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oldest, second_oldest, *others = cat_ages
print(oldest, second_oldest, others) # 0 1 [2, 3, 4, 5, 6, 7, 8, 9]
- 拆分数据结构并把其中的数据赋给变量时,可以用带星号的表达式,将结构中无法与普通变量相匹配的内容捕获到一份列表里。这种带星号的表达式可以出现在赋值符号左侧的任意位置,它总是会形成一份含有零个或多个值的列表
- 在把列表拆解成互相不重叠的多个部分时,这种带星号的unpacking方式比较清晰,而通过下标与切片来实现的方式则很容易出错
4. 用sort方法的key参数来表示复杂的排序逻辑
一般的sort排序可以直接调用排序
numbers=[93,86,11,68,70]
numbers.sort()
print(numbers) # [11, 68, 70, 86, 93]对与某些特殊的结构需要排序的话 可以通过lambda表达式去指定排序的key
class Tool:
def __init__(self,name,weight):
self.name = name
self.weight = weight
def __repr__(self):
return f'Tool({self.name!r},{self.weight})'
tools = [
Tool('leve1',3.5),
Tool('hammer',0.25),
Tool('screwdriver',0.5),
Tool('chise1',0.25),
]
print('Unsorted:', repr(tools)) # Unsorted: [Tool('leve1',3.5), Tool('hammer',0.25), Tool('screwdriver',0.5), Tool('chise1',0.25)]
tools.sort(key=lambda x: x.name)
print('Sorted: ', tools) # Sorted: [Tool('chise1',0.25), Tool('hammer',0.25), Tool('leve1',3.5), Tool('screwdriver',0.5)]
# 当然 也可以通过元组实现多个条件的排序
tools.sort(key=lambda x: (x.weight, x.name))
print('Sorted: ', tools) # Sorted: [Tool('chise1',0.25), Tool('hammer',0.25), Tool('screwdriver',0.5), Tool('leve1',3.5)]
# 也可以通过对一元减操作符(如果支持的话)对元组第一个条件升序, 第二个条件降序
tools.sort(key=lambda x: (-x.weight, x.name))
print('Sorted: ', tools) # Sorted: [Tool('leve1',3.5), Tool('screwdriver',0.5), Tool('chise1',0.25), Tool('hammer',0.25)]对于字符串也可以通过特殊处理后排序
places = ['home', 'work', 'New York', 'Paris']
places.sort()
print('Case sensitive:', places) # Case sensitive: ['New York', 'Paris', 'home', 'work']
places.sort(key=lambda x: x.lower())
print('Case insensitive:', places)# Case insensitive: ['home', 'New York', 'Paris', 'work']
- 列表的sort方法可以根据自然顺序给其中的字符串、整数、元组等内置类型的元素进行排序
- 普通对象如果通过特殊方法定义了自然顺序,那么也可以用sort方法来排列,但这样的对象并不多见
- 可以把辅助函数传给sort方法的key参数,让sort根据这个函数所返回的值来排列元素顺序,而不是根据元素本身来排列
- 如果排序时要依据的指标有很多项,可以把它们放在一个元组中,让key函数返回这样的元组。对于支持一元减操作符的类型来说,可以单独给这项指标取反,让排序算法在这项指标上按照相反的方向处理
- 如果这些指标不支持一元减操作符,可以多次调用sort方法,并在每次调用时分别指定key函数与reverse参数。最次要的指标放在第一轮处理,然后逐步处理更为重要的指标,首要指标放在最后一轮处理
5. 不要过分依赖给字典添加条目时所用的顺序
从Python 3.6开始,字典会保留这些键值对在添加时所用的顺序,而且Python 3.7版的语言规范正式确立了这条规则
在Python 3.5与之前的版本中,dict所提供的许多方法(包括keys、values、items与popitem等)都不保证固定的顺序
内置的collections模块早就提供了这种能够保留插入顺序的字典,叫作OrderedDict。它的行为跟(Python 3.7以来的)标准dict类型很像,但性能上有很大区别。如果要频繁插入或弹出键值对(例如要实现least-recently-used缓存),那么OrderedDict可能比标准的Python dict类型更合适
- 从Python 3.7版开始,我们就可以确信迭代标准的字典时所看到的顺序跟这些键值对插入字典时的顺序一致
- 在Python代码中,我们很容易就能定义跟标准的字典很像但本身并不是dict实例的对象。对于这种类型的对象,不能假设迭代时看到的顺序必定与插入时的顺序相同
- 如果不想把这种跟标准字典很相似的类型也当成标准字典来处理,那么可以考虑这样三种办法。第一,不要依赖插入时的顺序编写代码;第二,在程序运行时明确判断它是不是标准的字典;第三,给代码添加类型注解并做静态分析