生成器的理解
生成器与列表相关
我们知道一行代码就可以生成一个列表 [x for x in range(num)]
1 | my_list = [[x, x % 2] for x in range(10)] |
[[0, 0], [1, 1], [2, 0], [3, 1], [4, 0], [5, 1]]
1 | my_list = ([x, x % 2] for x in range(6)) |
<generator object
at 0x069B8970>
第二个代码输出了一个 generator(生成器) 对象
区别是:
第一个代码用的[]
第二个代码用的()
这就是生成器,不直接输出列表
可以通过 next() 方法调用这个生成器里的元素
1 | print(next(my_list)) |
[0, 0]
[1, 1]
[2, 0]
[3, 1]
[4, 0]
[5, 1]
这有什么区别呢?
现在把运算加大,
考虑这两行代码的运行时间,谁长谁短呢?
来个装饰器计时,看看两者的区别
1 | my_list1 = [[x, x % 2] for x in range(6666666)] |
1 | import time |
方法 calculate_func_1 , 消耗了 3356.3896 ms
方法 calculate_func_2 , 消耗了 0.0181000000001319 ms
从返回结果看出,方法二,也就是生成器所消耗的时间远小于方法一
这是因为
方法一:直接生成了 6666666 个元素并封闭进列表,耗时并占用大量内存
方法二:只记录了设定的算法规则,这里的 for x in range(6666666) 就是不断 +1 的规则
生成器只需记住这个规则,不需要一次性地将列表封装,
等在需要的时候,再按规则运算一下,然后将结果返回
几乎不占内存
总结,这就是生成器
它是根据一定的规律算法生成的,
当我们去遍历它的时候,
它可以通过特定的算法不断的推算出相应的元素,
边运行边推算结果,从而节省了很多空间。
继续体会这两者的区别
方法1将结果直接存入列表,for 循环只是将现存列表简单遍历打印出来
方法2是需要的时候才运算提取,每次 for 循环调用一次规则:([x, x % 2] for x in range(15))
1 | @log_func_time |
魔法关键词 yield
yield 与 return 关键词都是运用于函数中返回值
return 在函数中执行这个关键词就会返回相应的结果终止函数的运行
1 | def foo(): |
3
函数遇到 return 关键字就返回,而后的语句不再被执行到
再来看 yield 关键字
1 | def foo(): |
<class ‘generator’>
直接调用生成器函数没有返回结果
打印出对象类型为 generator(生成器)
一个函数里定义了 yield 关键字,那么这个函数就是一个生成器
可以用 next 方法使用生成器
1 | next(f) |
1111
2222
3333
4444
StopIteration
前四个 next 依次返回打印
第五个 next 没有数据了,返回 StopIteration
再遍历一下看看:
1 | for item in f: |
1111
None
2222
None
3333
None
4444
None
返回了 None, 这是因为 yield 行并没有具体返回结果
再来看 yield 后加上返回内容的情形:
1 | def foo(): |
第1次 yield
第2次 yield
第3次 yield
第4次 yield
以上仅仅展示简单的示例,也求快速理解 yield
生成器是按一定的规律算法生成的列表
当我们去遍历它的时候,它可以通过特定的算法不断的推算出相应的元素,
边运行边推算结果,从而节省了很多空间。
所以 yield 一般是用在 for 循环中以一定规则生成
简单运用举例:输出18以内的偶数
1 | def foo(): |
0
2
4
6
8
10
12
14
16
18
另外,yield 后面可以跟常用的数据类型,
比如:string、int、dict 等
比如:
1 | yield { |
再比如 Scrapy 爬虫框架中经常要 yield item
1 | for img in response.xpath('//img[@referrerpolicy]'): |