生成器是什么?
生成器(generator)是一种返回一个值的迭代器,每次从该迭代器取下一个值。
我暂且把它理解为一个惰性的迭代器,也就是说我用一次next方法,才去执行一次生成器。而不用的时候,就保持原来的状态不动。
生成器的类别
生成器有两种:生成器表达式和生成器函数
生成器表达式
生成器表达式是用圆括号“()”与for循环结合来创建生成器, 返回一个新的生成器对象。
print(type((i for i in range(5)))) # generator
这种生成器表达式被成为隐式生成器,它是禁止使用 yield 和 yield from 表达式。
生成器函数
如果一个函数包含 yield 指令,那么该函数调用的返回值是一个生成器对象。
调用函数并不会执行,只会返回一个生成器对象。当显示或隐示地调用函数的next方法的时候才会真正执行里面的代码(由此可以看出生成器就是一个懒懒的迭代器)。
def fun():
for i in range(5):
print("Start...")
yield i
print(fun()) # <generator object fun at 0x00000203EE77B660>
# 调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。
print(next(fun()))
# 输出:
# Start...
# 0
for j in fun():
print(j)
# 输出:
# Start...
# 0
# Start...
# 1
# Start...
# 2
# Start...
# 3
# Start...
# 4
由上面代码运行结果可知,调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。
那么yield关键字具体是如何作用呢?
yield关键字可以暂停一个函数并返回此时的中间结果,再次执行时从上次返回的yield语句处继续执行。因此,yield就提供了一个返回此时结果(提供了return的功能),并保存执行环境且在下一次next方法调用时恢复的作用。
那么yield关键字应该放在什么位置呢?后面又该跟什么变量呢?
其实很简单,就是看你需要在你的函数体哪个位置得到哪个值,就放在哪里且后面就跟什么值,即可。如下:
- 在遍历该生成器时,得到计算前的斐波那契数列的第一项值:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield a
a, b = b, a + b
n = n + 1
for i in fib(6):
print(i)
# 输出:
# 0
# 1
# 1
# 2
# 3
# 5
- 在遍历该生成器时,得到计算前的斐波那契数列的第一和第二项值:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield a, b
a, b = b, a + b
n = n + 1
for i in fib(6):
print(i)
# 输出:
# (0, 1)
# (1, 1)
# (1, 2)
# (2, 3)
# (3, 5)
# (5, 8)
- 在遍历该生成器时,得到计算后的斐波那契数列的全部值,并用列表返回:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
a, b = b, a + b
n = n + 1
yield [a, b, n]
for i in fib(6):
print(i)
# 输出:
# [1, 1, 1]
# [1, 2, 2]
# [2, 3, 3]
# [3, 5, 4]
# [5, 8, 5]
# [8, 13, 6]
那么生成器又如何停止呢?它会遍历完就自动结束吗?
- 若是for循环调用生成器,遍历完即自动结束。
- 若不是用for循环调用生成器,而是用next()函数调用生成器,会出现计算到最后一个元素(没有更多的元素)时,会抛出StopIteration的错误。因此一般使用for循环迭代生成器。
在生成器迭代过程中,遇到return语句,将会返回return后的“返回值”作为StopIteration异常说明,并终止迭代,如下:
# 用next()函数调用生成器,并使用return 返回值
def fib(max):
n, a, b = 0, 0, 1
while n < max:
a, b = b, a + b
n = n + 1
yield [a, b, n]
return '迭代一次后不再进行迭代'
g = fib(6)
print(next(g)) # [1, 1, 1]
print(next(g)) # StopIteration: 迭代一次后不再进行迭代
print(next(g))
print(next(g))
print(next(g))
print(next(g))
生成器高级用法
一、在遍历生成器的时候,能够从外部传入值到生成器的内部。
那么生成器怎么做呢?
首先需要在生成器的函数体内定义接收外部传入值的变量,语法为:
receive = yield value
其实很容易看出就是将原本的“yield关键字 值”赋值给一个变量receive。此时的yield关键字不仅有return、暂停函数和保存现场的功能,还具有从外部得到一个值并复制给变量receive的功能。真真是强大极了~
那么外部又如何传值呢?
外部可以通过“生成器对象.send(值)”方法,将值传递给生成器内部,也通过yield关键字传递给变量receive。同时“生成器对象.send(值)”方法也如同隐式或显式使用next方法一样,可以执行生成器。
那么具体流程又是如何呢?
其实很简单,就是在原来的基础上,多了一个从外部获取值并赋值给变量的功能,其他流程仍然不变。依旧是运行到yield关键字时,停止执行并返回yield后的变量,对现场进行保存。然后,再是多的步骤为从外部获取值并赋值给变量。下一次执行时,从yield后开始,并恢复现场。
实例
def fun():
for i in range(5):
print("Start...")
receive = yield i
print(receive)
f = fun()
print(next(f))
print('-------------------')
print(next(f))
print('-------------------')
print(next(f))
print('-------------------')
print(f.send("hello"))
输出结果为:
Start...
0
-------------------
None
Start...
1
-------------------
None
Start...
2
-------------------
hello
Start...
3
二、yield from 生成器
yield from允许一个生成器将其部分操作委派给另一个生成器。其中,“yield from”命令在生成器中的语法为:
yield from generator(子生成器)/iterable(迭代器)
也就是说需要两个生成器,其中一个是含有“yield from”关键字的生成器,被称为“委托生成器”。另外一个被称为“子生成器”,由“委托生成器”通过“yield from”关键字进行调用。具体写法如下“f2()”、“f1(x)”的代码所示:
(又或者是一个生成器,一个迭代器。生成器含有“yield from”关键字的语法,称之为“委托生成器”。其表现形式为:
def fun(): yield from range(5) for i in fun(): print(i) # 输出: # 0 # 1 # 2 # 3 # 4
,但这种形式一般不常用。)
def f1(x):
receive = yield x
return receive
def f2():
for i in range(5):
rlt = yield from f1("你好")
print(rlt)
f = f2()
print(next(f))
print('-------------------')
print(f.send("世界"))
输出结果为:
你好
-------------------
世界
你好
那么“yield from”关键字有什么作用呢?
“yield from”关键字相当于把“委托生成器”的“yield”全部的功能委托给了“子生成器”。也就是说“委托生成器”不再具有暂停、return、保存现场和恢复现场、获取外部值的功能了,而需要通过“子生成器”的“yield”来实现全部功能。通过上面代码和下面代码的对比,其中“子生成器”的获取到外部值之后,return返回给“委托生成器”。若“子生成器”不返回则“委托生成器”获得“None”值。
def f1(x):
receive = yield x
def f2():
for i in range(5):
rlt = yield from f1("你好")
print(rlt)
f = f2()
print(next(f))
print('-------------------')
print(f.send("世界"))
输出结果为:
你好
-------------------
None
你好
需要注意的第一点是:如果对“子生成器”产生 StopIteration异常,“委托生成器”恢复继续执行yield from后面的语句。一直到“委托生成器”执行完成,才会返回“子生成器”的StopIteration异常。可以粗暴的理解为:虽然“子生成器”元素遍历完了,但是“委托生成器”元素还没遍历完,就不能抛出StopIteration异常。类似于“子生成器”和“委托生成器”是一个共同体,“你的没有了可以用我的,我的没有了那就是真的大家都没有了”。
def f1():
return
yield "hello"
def f2():
for i in range(5):
yield from f1()
print("f2")
f = f2()
print(next(f))
输出结果为:
f2
f2
f2
f2
f2
StopIteration
需要注意的第二点是:如果委托生成产器的 close()方法被调用,那么子生成器有close()的话也将被调用。close()方法也就是手动使得生成器提前到达元素被访问完的状态。
def f1(x):
yield x
def f2():
for i in range(5):
yield from f1("f2")
f11 = f1("f1")
f22 = f2()
print(next(f11))
print('-------------------')
print(next(f22))
print('-------------------')
f22.close()
print(next(f11))
print('-------------------')
print(next(f22))
输出结果为:
f1
-------------------
f2
-------------------
StopIteration
-------------------
StopIteration