27. 用列表推导提到map和filter

  1. # 推导比使用map和filter看起来更易懂,简洁
  2. arr = [x ** 2 for x in source if x < 2]
  3. my_set = {x ** 2 for x in source}

同样字典和集合也有推导机制
当然,创建列表,字典,集合的时候也可以用推导的方式,可以避免使用range来初始化

28. 控制推导逻辑的子表达式不超过两个

比如遇到二维数组的时候可能遇到以下的情形,这样还能看得清其含义和逻辑,但是如果再来几个循环,就会比较复杂了,还不如写成多层循环来的清晰。

  1. arr = [[x ** 2 for x in row] for row in source]
  2. arr = [x for row in mat for x in row]

推导的时候可以使用多个if,同级循环中多个if相当于and,当然也可以直接在if里面包含两个条件,用and链接。

29. 用赋值表达式消除推导中的重复代码

赋值表达式就是第10条里讲的’:=’,即在推导中涉及条件判断的时候使用赋值表达式消除重复代码

  1. # 使用两次func
  2. dic = {name: func(name) for name in arr if func(name)}
  3. # 使用一次func
  4. dic = {name: fname for name in arr if (fname := func(name)}

30. 不要让函数直接返回列表,应该让他逐个生成列表里的值

对于可以返回列表的函数,替换成用生成器 yield。

    1. 返回列表的做法代码相比于yield更加复杂
    1. 返回列表的话,需要先将值都存在列表中,然后才能返回,而生成器不会有这个问题,内存消耗相对更低

缺点:生成器的缺点在于无法重复使用函数所返回的生成器

  1. def func(n):
  2. arr = []
  3. for i in range(n):
  4. arr.append(i)
  5. return arr
  6. def func_yield(n):
  7. for i in range(n):
  8. yield i

31. 谨慎地迭代函数所收到的参数

即根据上面一条的缺点,我们要注意是否重复使用了生成器
接上一条,下面代码中sum函数已经遍历完一次迭代器了,因此,for循环不会进行。

  1. it = func_yield(10)
  2. total = sum(it)
  3. for i in it:
  4. print(i)

一种方式是将迭代器对应的数据复制到列表中存起来,这其实就和原始函数func返回列表一样了,会占用比较大的内存,但是确实能完成任务。
另一种方式,就是每次需要生成器的时候都重新调用一次,如下面代码

  1. total = sum(func_yield(10))
  2. for i in func_yield(10):
  3. print(i)
  4. # 如果是在函数里
  5. def cal(f):
  6. total = sum(f())
  7. for i in f():
  8. print(i)
  9. def getit():
  10. return func_yield(10)
  11. cal(getit)
  12. cal(lambda: func_yield(10))

第三种方式,自己构建简单的容器类,sum和for的时候相当于各自构建了一个生成器

  1. class MyContainer:
  2. def __init__(self, n):
  3. self.n = n
  4. def __iter__(self):
  5. for i in range(self.n):
  6. yield i
  7. def cal(c):
  8. total = sum(c)
  9. for i in c:
  10. print(i)
  11. cal(c)

32. 考虑用生成器表达式改写数据量较大的列表推导

列表推导相当于是遍历完之后把数据都存在列表里,会占用较大内存,如果数据量很大,则可能内存耗尽。
将[]替换为()就可以转变为生成器表达式了,哈哈,可能看着有点像tuple元组,但是不是哦。
(x**2 for x in range(3))

33. 通过yield from 把多个生成器连起来用

如果要是用多个生成器,可以使用yield from,避免重复使用for;yield from的性能比在for循环内用yield好,简单理解就是yield from可以替换掉一个for,例子

  1. arr = [1, 2, 3]
  2. def func_1(arr):
  3. yield from arr
  4. arr = [[1, 2], [3, 4]]
  5. def func_2(arr):
  6. for i in range(2):
  7. yield from arr[i]

34. 不要用send给生成器注入数据

yield可以简单的构建出生成器,他是对外输出的,那么send则是调用方往生成器传递数据,比如可以通过send不同的值动态改变生成器产生的数据。
send的运作方式是将值传递给上一个yield,然后运行到下一个yield,如果通过send而不是next来运作生成器时,第一次send需要send None,因此第一次没有更前面的yield了。这样是读代码的不易读,需要阅读代码的人对这个特性熟悉。
想要实现动态改变,可以使用迭代器组合的方式。可以将需要改变的值存在另一个生成器中,然后在当前生成器中不断遍历就可实现动态改变。

35. 不要通过throw变换生成器的状态

通常使用throw,是内部捕获到异常后改变相应的状态,但是这个可以用容器对象+额外的函数来实现,在特定时刻调用函数来改变状态即可,使用throw容易产生各种嵌套

36. 考虑用itertools拼装迭代器与生成器

itertools中有很多可以方便使用的函数来帮助我们安排迭代器之间的关系,如chain,repeat,cycle等等,多利用现有工具