要说生成器,就必须首先要知道列表的概念;
我们创建一个如下的列表:
ls = [1,2,3,4,5,6,7,8,9]
那么就开辟了一个门牌号为ls的内存区,然后真的把1,2,3,4,5,6,7,8,9这几个数字放到了内存中;
如果我们在for循环中要根据i的值来获取一个值的话,我们可以把数据放在上面的ls中,然后通过ls[i]来获取;这样的
方式存在巨大的缺点,就是要事先准备好ls中的所有数值;如果很多的话会造成响应慢,内存溢出等问题;
所以就有有了生成器的概念,生成器会指定一个公式,然后每次循环都根据公式计算出当前的值,不用事先生成
所有的值。
一、最简单的生成器
# -*- coding:utf-8 -*- # Author: Evan Mi # 这是一个最基本的生成器,对应的公式是i*2,i从0到9 # 这样我们就可以通过迭代的方式来访问生成的每一个值了 # 它的缺点就是,只能逐一迭代,不能随意指定i,也不能往前迭代 my_gen_01 = (i*2 for i in range(10)) """ 可以看到my_gen是一个generator对象 """ print(my_gen_01) # <generator object <genexpr> at 0x00000000024FCBA0> # generator对象是一个迭代器对象,所以直接通过next()方法来迭代 while True: try: print(next(my_gen_01)) # 迭代(根据公式计算) except StopIteration as e: # 迭代到没有元素后会抛出StopIteration print(e.value) break """ 运行结果: 0 2 4 6 8 10 12 14 16 18 None """ # 用for循环迭代,不会有异常抛出 # 需要新建一个,因为上一个已经被迭代完了(生成器是一次性用品) my_gen_02 = (i*2 for i in range(10)) for val in my_gen_02: print(val) """ 运行结果: 0 2 4 6 8 10 12 14 16 18 """
二、用函数来实现复杂的生成器
# -*- coding:utf-8 -*- # Author: Evan Mi # 引用别人的斐波那契数列生成函数 """函数运行到yield的时候,就会暂停,并将yield关键字之后的值传到函数外,函数暂停在这里,直到等到下一次迭代""" def fib(max_num): n, a, b = 0, 0, 1 while n < max_num: yield b a, b = b, a+b n += 1 # 定义了一个生成器,这个生成器每次迭代得到的值就是yield带出来的值 fb = fib(10) # 用for循环迭代 for val in fb: print(val) """ 运行结果: 1 1 2 3 5 8 13 21 34 55 """ # 用next迭代 fb1 = fib(10) while True: try: print(next(fb1)) except StopIteration as e: print(e.value) break """ 运行结果: 1 1 2 3 5 8 13 21 34 55 None """
可以看到,在使用for循环迭代的时候,并不需要自己去处理迭代停止的异常;而用next自己处理就需要;而且
我们每次的e.value都是none;现在给函数加上返回值,如下:
def fib(max_num): n, a, b = 0, 0, 1 while n < max_num: yield b a, b = b, a+b n += 1 return 'done'
对这样的函数,我们用next调用;
# 用next迭代 fb1 = fib(10) while True: try: print(next(fb1)) except StopIteration as e: print(e.value) break """ 运行结果: 1 1 2 3 5 8 13 21 34 55 done """
可以看到e.value变成了done;所以一个函数如果作为了生成器,那么return的值就是迭代停止时的异常信息;
yield能带出函数中的值,那么yield就是函数内部与外界的媒介,那么自然也能从外部代值回来:
修改函数如下:
def fib(max_num): n, a, b = 0, 0, 1 while n < max_num: xxx = yield b # 后面的b在运行到yield时被带出去,前面的xx在激活yield时被带进来 print(xxx) a, b = b, a+b n += 1 return 'done'
执行如下的测试:
gx = fib(10) # 一开是只能调用next,因为这时只是创建了一个生成器,还没有yield变量 print('out;', next(gx)) # 把233给yield,同时往下运行到下一次循环到yield处,并带回yield的值 print('out;', gx.send(233)) print('out;', gx.send(333)) print('out;', gx.send(433)) # 循环到下一次的yield,并带回yield的值(其实就是把None给了yield) print('out;', next(gx))
运行结果如下:
out; 1 第一次只能从yield带出值
233 通过yield传入了233,在函数内部打印
out; 1 带出1
333 传入333
out; 2 带出2
433 传入433
out; 3 带出3
None 传入None
out; 5 带出5