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__ 定义),使用属性来管理状态。
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) 生成有放回选择的所有集合。