Chapter 4 Comprehensions and Generators
27. 用列表推导来取代 map 和 filter
- 使用列表推导:表达式替代
map中的 lambda 函数,for替代map迭代,if替代filter筛选。 - 字典与集合也支持推导表达式。
28. 不要使用含有两个以上表达式的列表推导
- 列表推导内的多个
for表达式会按从左到右的顺序评估; - 列表推导内的多个
if条件默认形成and表达式; - 最好不要使用两个以上的
for/if表达式。
29. 使用赋值表达式来避免推导式中的重复
30. 考虑用生成器来改写直接返回列表的函数
- 调用生成器函数时返回迭代器,在调用内置函数
next()时,执行代码到yield处,产出值。最后抛出StopIteration异常; - 用生成器替代列表,代码更简洁,也避免了内存占用过大的问题。
31. 在参数上面迭代时,要多加小心
- 迭代器只能产生一轮结果,后续的迭代并不会抛出异常;
- 解决方案:
- 创建列表:可能出现内存耗尽问题;
- 传入迭代器生成函数:需要单独定义一个生成函数(用 lambda),显得生硬;
- 自定义可迭代的容器类:定义
__iter__方法,返回一个新的生成器。将该可迭代的容器实例传入函数即可,而不是传入迭代器(区分方式:对迭代器调用iter()每次都会返回自身,而对可迭代对象调用iter()每次会返回不同的迭代器对象)。
32. 用生成器表达式来改写数据量较大的列表推导
- 生成器表达式可以互相组合;
- 迭代器是有状态的,用过一轮后不能反复使用。
33. 使用 yield from 组合生成器
- 可以用
yield from来替代for循环 + 迭代yield的组合。
34. 不要用 send 向生成器传入数据
- 使用一般的迭代方式读取生成器时,
yield的返回值为None; it.send()方法可以与next()一样返回新生成的数值,同时向生成器传入数值;send()、next()等函数的作用是将程序控制权交还给生成器。第一次调用时生成器还没有执行,第一次必须传入None;- 在组合使用生成器时,
send()的语法会使得程序难懂,因此不建议使用; - 迭代器有状态,因此可以将同一个迭代器对象传入多个函数,实现接连处理;
- 使用迭代器作为输入存在唯一的问题:需要线程安全。
35. 不要使用 throw 控制生成器的状态
it.throw()会将控制权交给生成器,并在继续执行时(yield语句处)抛出参数中指定的异常(可以捕获);- 可以用可读性更强的方式来实现生成器与主程序之间的通信:定义为可迭代对象(生成器用
__iter__定义),使用属性来管理状态。
36. 使用 itertools 管理迭代器与生成器
chain(*iters)顺序连接多个迭代器;repeat(v, times=inf)重复生成定值,直到次数限制;cycle(iter)循环顺序生成迭代器中的元素;tee(iter, n)将一个迭代器复制为多个;zip_longest(*iters, fillvalue=None)直到所有迭代器都终止才停止;islice(iter, ...)迭代器的懒惰切片,参数与切片一致;takewhile(func, iter)依次返回迭代器元素,直到func(x)为False(包含此元素)后终止;dropwhile(func, iter)依次跳过迭代器元素,直到func(x)为False,之后返回包含此元素的所有剩余元素;filterfalse(func, iter)为filter的相反版本;accumulate(iter, func=add)为累加器(与reduce类似),同样返回迭代器,各元素表示累加到对应位置的值。参数为二元运算;product(*iters, repeat=1)生成笛卡尔积(可替换嵌套列表生成式);permutations(iter, r=None)生成选择r个不同元素的所有顺序;combinations(iter, r=None)生成选择r个不同元素的所有集合;combinations_with_replacement(iter, r=None)生成有放回选择的所有集合。