27. 用列表推导提到map和filter
# 推导比使用map和filter看起来更易懂,简洁
arr = [x ** 2 for x in source if x < 2]
my_set = {x ** 2 for x in source}
同样字典和集合也有推导机制
当然,创建列表,字典,集合的时候也可以用推导的方式,可以避免使用range来初始化
28. 控制推导逻辑的子表达式不超过两个
比如遇到二维数组的时候可能遇到以下的情形,这样还能看得清其含义和逻辑,但是如果再来几个循环,就会比较复杂了,还不如写成多层循环来的清晰。
arr = [[x ** 2 for x in row] for row in source]
arr = [x for row in mat for x in row]
推导的时候可以使用多个if,同级循环中多个if相当于and,当然也可以直接在if里面包含两个条件,用and链接。
29. 用赋值表达式消除推导中的重复代码
赋值表达式就是第10条里讲的’:=’,即在推导中涉及条件判断的时候使用赋值表达式消除重复代码
# 使用两次func
dic = {name: func(name) for name in arr if func(name)}
# 使用一次func
dic = {name: fname for name in arr if (fname := func(name)}
30. 不要让函数直接返回列表,应该让他逐个生成列表里的值
对于可以返回列表的函数,替换成用生成器 yield。
- 返回列表的做法代码相比于yield更加复杂
- 返回列表的话,需要先将值都存在列表中,然后才能返回,而生成器不会有这个问题,内存消耗相对更低
缺点:生成器的缺点在于无法重复使用函数所返回的生成器
def func(n):
arr = []
for i in range(n):
arr.append(i)
return arr
def func_yield(n):
for i in range(n):
yield i
31. 谨慎地迭代函数所收到的参数
即根据上面一条的缺点,我们要注意是否重复使用了生成器
接上一条,下面代码中sum函数已经遍历完一次迭代器了,因此,for循环不会进行。
it = func_yield(10)
total = sum(it)
for i in it:
print(i)
一种方式是将迭代器对应的数据复制到列表中存起来,这其实就和原始函数func返回列表一样了,会占用比较大的内存,但是确实能完成任务。
另一种方式,就是每次需要生成器的时候都重新调用一次,如下面代码
total = sum(func_yield(10))
for i in func_yield(10):
print(i)
# 如果是在函数里
def cal(f):
total = sum(f())
for i in f():
print(i)
def getit():
return func_yield(10)
cal(getit)
cal(lambda: func_yield(10))
第三种方式,自己构建简单的容器类,sum和for的时候相当于各自构建了一个生成器
class MyContainer:
def __init__(self, n):
self.n = n
def __iter__(self):
for i in range(self.n):
yield i
def cal(c):
total = sum(c)
for i in c:
print(i)
cal(c)
32. 考虑用生成器表达式改写数据量较大的列表推导
列表推导相当于是遍历完之后把数据都存在列表里,会占用较大内存,如果数据量很大,则可能内存耗尽。
将[]替换为()就可以转变为生成器表达式了,哈哈,可能看着有点像tuple元组,但是不是哦。
(x**2 for x in range(3))
33. 通过yield from 把多个生成器连起来用
如果要是用多个生成器,可以使用yield from,避免重复使用for;yield from的性能比在for循环内用yield好,简单理解就是yield from可以替换掉一个for,例子
arr = [1, 2, 3]
def func_1(arr):
yield from arr
arr = [[1, 2], [3, 4]]
def func_2(arr):
for i in range(2):
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等等,多利用现有工具