第四章 推导与生成
1.用列表推导取代map与filter
python中可以根据某个序列或可迭代对象派生出一份新列表,称为列表推导:
它还可以过滤原列表,把某些输入值对应的计算结果从输出结果中排除:
相当于同时使用了map与filter,但是比那种写法要更易懂
字典与集合也有相应的推导机制,分别叫做字典推导与集合推导
2.控制推导逻辑的子表达式不要超过两个
列表推导支持多层循环,例如将二维列表转换为一维:
多层循环也可以用来设置两层深的结构
但是更深的层次会导致列表推导过于难懂,不如直接使用for循环构造:
顺带一提,多个if条件在同一级别是and关系:
在表示推导逻辑时,最多之应该写两个子表达式(例如两个if条件、两个for循环、或者一个if条件与一个for循环)。只要实现的逻辑比这个要复杂,就应该采用普通的if与for语句实现,并可以考虑编写辅助函数。
3. 用赋值表达式消除推导中的重复代码
假设要对订单中每个货物以8个一批的方式发货,并要判断货物库存是否能够够一批标准,通过order与stock的相同字段来进行匹配。
这里调用了两次get_batches方法,在只更改其中一个方法时会导致方法错误。因此要使用赋值表达式来消除推导中的重复代码。
4. 不要让函数直接返回列表,应该让它逐个生成列表里的值
写一段代码来找出文本中每个单词的开头位置
改用生成器会更好,首先,会更加简洁:
其次,不用一次性构造整个列表,例如,不用先读出全文,可以每行每行递进
5. 函数在迭代参数前要先判断参数的类型
源于这样一种考量:
若传来的是一个迭代器或生成器,则只能产生一次结果,不能够遍历两边
使用list构造列表时也会出现同样问题,导致已经耗完数据的迭代器返回空列表
先copy出所有结果又会导致数据过多,从而有内存崩溃的风险
因此最好的方式是在函数的两次迭代sum(numbers)
与for value in numbers
中每次都先拿到一个新的迭代器:
python中提供了一个接口用于实现迭代器协议(iterator protocol):
Python执行for x in foo
这样的语句时,实际上会调用iter(foo)
,也就是把foo传给内置的iter函数。这个函数会触发名为foo.__iter__
的特殊方法,该方法必须返回迭代器对象(这个迭代器对象本身要实现__next__
特殊方法)。最后,Python会用迭代器对象反复调用内置的next函数,直到数据耗尽为止(如果抛出StopIteration异常,就表示数据已经迭代完了)。
因此只需要新建一个容器类:
所以,在编写函数处理迭代时,最好先检查一下迭代的类型,防止在函数中出现数据耗尽的问题,并报出TypeError错误来提示接口使用者:
6. 考虑用生成器表达式改写数据量较大的列表推导
例如读写文件时,直接使用列表推导需要将文件中的每行文本都载入内存,对于庞大的文件会导致内存耗尽。
要想处理大规模数据,可以使用生成器表达式,生成器表达式每次给出一个结果:
同时生成器表达式还可以进行嵌套:
使用时可以用内置的next
函数取得结果:
但是要注意迭代器在跑完一轮后会导致数据耗尽,无法继续使用,此时需要新生成一个迭代器。
7.通过yield from连接多个生成器
连接多个生成器可以采用上面的方式,采用多个for遍历。
可以改成使用yield from来简化,且这种方式速度更快,因此优先使用yield from来连接生成器:
8. 不要用send给生成器注入数据
send,调用send方法时传入的参数会成为上一条yield表达式的值,生成器拿到这个值后,推进到下一条yield。在刚开始推进生成器时,是从头执行的,而不是从某一条yield继续,因此首次调用时只能传None。
由于这种逻辑上的复杂性,这样写会导致代码可读性较差,因此应当用多个yield from来代替。
9. 不要通过throw变换生成器的状态
通过iter.throw为生成器注入异常,则异常会被重新抛出。
这样的方式通常被用来改变生成器的状态,例如如下的一个计时器的重置。
然而这样的双向通信逻辑复杂,不如采用如下的一个可迭代的容器对象:
10. 考虑用itertools拼装迭代器与生成器
如果要实现比较难写的迭代逻辑,可以查看itertools文档,去查找可能用得到的函数。
这些函数分为三大类:
连接多个迭代器
chain
chain可以把多个迭代器从头到尾连接成一个迭代器。
repeat
repeat可以不停地输出某个值:
cycle
cycle可以循环地输出各项元素:
tee
分裂多个平行的迭代器,每个迭代器推进的速度可能不一致
zip_longest
与zip类似,如果长度不同,会用fillvalue来填补耗尽的那些迭代器留下的空缺:
过滤源迭代器中的元素
islice
按照下标切割源迭代器,给出起点、终点、步进值。
takewhile
一直获取元素,直到某元素让测试函数返回False为止。
dropwhile
会一直跳过元素,知道某个元素让测试函数返回True为止,然后开始逐个取值。
filterfalse
与filter相反,逐个返回False的那些元素
用源迭代器中的元素合成新元素
accumulate
取出元素,与累加结果一起传给表示累加逻辑的函数,然后输出那个函数的计算结果作为新的累计值。
product
获取元素计算笛卡尔积,可以取代那种多层嵌套的列表推导代码:
permutations
输出所有有序排列,如果元素相同但顺序不同算两种排列。
combinations
所有无序排列,即如果元素相同但顺序不同算同一种组合:
combinations_with_replacement
可放回的排列,允许同一个元素在组合里多次出现。