1、定义
通过列表生成式(列表推导式),我们可以直接创建一个列表。但是受到内存限制,列表容量有限。但是如果列表元素可以按照某种算法推算出来,那么我们就可以在循环的过程中不断推算出后续的元素,而不必直接把全部元素放置在内存中。
在python中,这种一边循环一边计算的机制,称为生成器(generator)。
如果数据量比较大的情况下,生成器要比类别推导式节省大量内存空间。
2、创建
-
方式1:通过推导式
g = (x ** 2 for x in range(11)) print(type(g))
-
方式2:在函数中包含yield关键字,调用该函数就创建一个生成器对象。
def generator_01(): for i in range(11): yield i ** 2 f = generator_01() print(type(f))
3、访问
如何访问生成器中的元素呢?
-
访问方式1:生成器.__next__()
-
访问方式2:next(生成器)
-
访问方式3:生成器.send(参数) ,在标题4中讲解
-
示例:
def generator_01(): for i in range(11): yield i ** 2 f = generator_01() print(type(f)) print(f.__next__()) print(next(f))
显然这样每次调用只能访问一个元素,那么当生成器为有限元素时,访问到最后一个元素后再次调用会发生什么呢?
0
1
File "F:/PycharmProjects/python-study/base/generator/test01.py", line 15, in <module>
4
print(next(f))
StopIteration
程序会抛出StopIteration异常,那么我们通过for循环和异常处理,就可以访问全部元素。
-
示例:打印含有n项的斐波那契数列
def fib(n): ''' 包括指定项的斐波那契数列 :param n: 项数 :return: 斐波那契数列生成器 ''' n1 = 1 n2 = 1 len = 0 while len < n: yield n1 n1, n2 = n2, n1 + n2 len += 1 f = fib(6) while True: try: print(next(f)) except StopIteration as e: print('访问结束') break
4、yield和send
下面我们通过程序断点看下yield执行顺序以及yield表达式及赋值问题。示例:
def gen():
i = 0
while i < 5:
temp = yield i
print('temp:', temp)
i += 1
return '没有值了'
g = gen()
for i in g:
try:
print(i)
except StopIteration as e:
print(e.value)
- 图示,当程序执行进入gen()函数后,由第4行直接转到第12行,
- 当程序再次进入gen()时,直接进入第4行。
- 循环执行上面步骤。
- yield作用:当程序执行到yiled时,计算yield表达式的值返回并记录返回位置,下次迭代从此继续执行
- temp的打印值全部为:None,当继续执行yield表达式之后的赋值时,并没有值可用所以是None
那么怎么给yield表达式左边的变量赋值呢?此时需要用到send方法。
def gen():
i = 0
while i < 5:
temp = yield i
print('temp:', temp)
i += 1
return '没有值了'
g = gen()
print(g.send(None))
print(g.send('呵呵'))
print(g.send('hello'))
# 打印结果
0
temp: 呵呵
1
temp: hello
2
- send第一次调用参数需要指定为None,因为第一次迭代执行到yield表达式就暂停了并不会给变量赋值
5、总结
- 生成器(generator)能够迭代的关键是它有一个next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。
- 带有 yield 的函数不再是一个普通函数,而是一个生成器generator。可用next()调用生成器对象来取值。next 两种方式 t.next() | next(t)。
- 可用for 循环获取返回值(等后面讲解迭代器和可迭代对象时详细讲解): 每执行一次,取生成器里面一个值,基本上不会用
next()
来获取下一个返回值,而是直接使用for
循环来迭代。 - yield相当于 return 返回一个值,并且记住这个返回的位置,下次迭代时,代码从yield的下一条语句开始执行。
- send() 和next()一样,都能让生成器继续往下走一步(下次遇到yield停),但send()能传一个值,这个值作为yield表达式整体的结果
- 生成器应用场景-协程,后面关于多线程协程的时候会讲解
参考博客地址:
参考视频地址:
- https://www.bilibili.com/video/BV1R7411F7JV
源代码仓库:https://gitee.com/gaogzhen/python-study