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) 生成有放回选择的所有集合。