生成器的定义通过列表生成式,我们可以直接创建一个列表 。但是,受到内存限制,列表容量肯定是有限的 。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了 。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间 。在Python中,这种一边循环一边计算的机制,称为生成器:generator 。
生成器的创建生成器可以用两种方式创建:
- 生成器表达式(里面是推导式,外面用圆括号)
- 生成器函数(用def定义,里面含有yield)
[]改成(),就创建了一个generator:li = [x * x for x in range(10)]print(li)# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]g = (x * x for x in range(10))print(g)# <generator object <genexpr> at 0x000001A72D5D2E08>生成器函数(yield)generator非常强大 。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现 。比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
def fib(max):n, a, b = 0, 0, 1while n < max:print(b)a, b = b, a + bn = n + 1return 'done'# 注意,这里的赋值语句a, b = b, a + b# 相当于# t = (b, a + b) # t是一个tuple# a = t[0]# b = t[1]# 但不必显式写出临时变量t就可以赋值 。上面的函数可以输出斐波那契数列的前N个数:print(fib(6))# 1# 1# 2# 3# 5# 8# done仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator 。也就是说,上面的函数和generator仅一步之遥 。要把
fib函数变成generator,只需要把print(b)改为yield b就可以了:def fib(max):n, a, b = 0, 0, 1while n < max:yield ba, b = b, a + bn = n + 1return 'done'这就是定义generator的另一种方法 。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:f = fib(6)print(f)# <generator object fib at 0x000001AB51492E08>生成器的调用调用生成器的方式:next()函数for循环for循环 +next()函数
li和g的区别仅在于最外层的[]和(),li是一个list,而g是一个generator 。我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过
next()函数获得generator的下一个返回值:g = (x * x for x in range(10))print(next(g))# 0print(next(g))# 1print(next(g))# 4print(next(g))# 9print(next(g))# 16print(next(g))# 25print(next(g))# 36print(next(g))# 49print(next(g))# 64print(next(g))# 81print(next(g))'''Traceback (most recent call last):File "D:/python_project/mxxl/test/test.py", line 18, in <module>print(next(g))StopIteration'''# 每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误 。for循环当然,上面这种不断调用next(g)实在是太变态了,我们可以使用for循环来调用generator,因为generator也是可迭代对象:g = (x * x for x in range(10))for i in g:print(i)# 0# 1# 4# 9# 16# 25# 36# 49# 64# 81所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误 。但是需要注意的是,当数据量过大时,会形成形成类似于死循环的效果(这里可以自己试验一下),所以就提出了下面的调用方法
for + next()
g = (x * x for x in range(10))# 调用几次循环几次for i in range(3): print(next(g))# 0# 1# 4注意点generator和函数的执行流程不一样:- 函数是顺序执行,遇到
return语句或者最后一行函数语句就返回 。
- 而变成generator的函数,在每次调用
next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行 。
def odd():print('step 1')yield 1print('step 2')yield(3)print('step 3')yield(5)调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:o = odd()next(o)# step 1next(o)# step 2next(o)# step 3next(o)'''Traceback (most recent call last):File "D:/python_project/mxxl/test/test.py", line 23, in <module>next(o)StopIteration'''可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行 。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错 。回到
fib的例子,我们在循环过程中不断调用yield,就会不断中断 。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来 。同样的,把函数改成generator后,我们基本上从来不会用
next()来获取下一个返回值,而是直接使用for循环来迭代:def fib(max):n, a, b = 0, 0, 1while n < max:yield ba, b = b, a + bn = n + 1return 'done'for i in fib(6):print(i)# 1# 1# 2# 3# 5# 8但是用for循环调用generator时,发现拿不到generator的return语句的返回值 。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:【python基础训练100题 补充 python基础:python三大器之生成器】
g = fib(6)while True: try:x = next(g)print('g:', x) except StopIteration as e:print('Generator return value:', e.value)break# g: 1# g: 1# g: 2# g: 3# g: 5# g: 8# Generator return value: done
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
