如有问题欢迎指出!
定义
Python中生成器是迭代器(Iterator) 的一种,每次遇到 yield
时函数会暂停并保存当前所有的运行信息,返回 yield
后的值, 并在下一次执行 next()
方法时从当前位置继续运行。
列表一次生成一组值,占用内存空间;生成器一次只产生一个值,用多少,取多少。
创建生成器(Generator)
如何创建生成器?
- 将列表生成式的中括号[]改为小括号(),得到生成器表达式;
- 定义一个包含
yield
关键字的函数——生成器函数,调用函数就是创建了一个生成器(generator)对象。
yield
相当于return
返回一个值,并且记住这个返回的位置保留当前信息,下次迭代时,代码从yield
的下一条语句继续执行。
这里大家可以思考下将generator_2(n)
中的yield
改为return
,每次调用函数的结果是什么。
# 列表生成式
list1 = [x*x for x in range(3)]
print(list1)
# 创建生成器
# 1.生成器表达式:列表生成式的中括号[]改为小括号()
generator1 = (x*x for x in range(3))
print(generator1)
# 2.函数中包含yield关键字,调用函数就是创建了一个生成器(generator)对象。
def generator_2(n):
for x in range(n):
yield x*x
generator2 =generator_2(3)
print(generator2)
Output:
[0, 1, 4]
<generator object <genexpr> at 0x000001F4C81E1AC0>
<generator object generator_2 at 0x000001F4C81E1DD0>
注意:调用生成器函数时generator2 =generator_2(3)
只是创建了一个生成器,并不会执行函数里面的内容。
获得生成器的值
- 重复调用
next()
方法 ,直到捕获一个异常; - 通过 for 循环对生成器进行迭代。
next()
方法:next(generator)
或generator.__next__()
def generator_2(n):
for x in range(n):
yield x*x
generator2 =generator_2(3)
print(generator2)
print(next(generator2))
print(generator2.__next__())
Output:
<generator object generator_2 at 0x000001F86F135EB0>
0
1
print(next(generator1))
print(next(generator1))
print(next(generator1))
print(next(generator1))
Output:
通过重复调用next(generaotr_1)
对生成器进行迭代得到相应值,直到得到最后一个元素。
当没有更多的元素时,继续使用next()
会出现StopIteration的错误。
推荐使用for循环对生成器进行迭代。
for 循环
for i in generator1:
print(i)
斐波那契数列
利用生成器生成斐波那契数列
(根据《算法精粹》改编)
斐波那契数列指的是这样一个数列:0,1、1、2、3、5、8、13、21、34、55……前两项为
f
i
b
(
0
)
=
0
fib(0)=0
fib(0)=0、
f
i
b
(
1
)
=
1
fib(1)=1
fib(1)=1,后面的每一项都等于它前两项之和,即
f
i
b
(
n
)
=
f
i
b
(
n
−
1
)
+
f
i
b
(
n
−
2
)
fib(n)=fib(n-1)+fib(n-2)
fib(n)=fib(n−1)+fib(n−2)。
def fib(n:int):
yield 0
if n>0:
yield 1
if n>1:
n_2,n_1 = 0,1
for _ in range(2,n+1):
n_2,n_1 = n_1,n_2+n_1
yield n_1
return 'done'
for i in fib(5):
print(i)
Ouput:
0
1
1
2
3
5
根据上面的输出结果,发现通过for循环对生成器进行迭代,得不到return语句的返回值。
如何获得生成器最后return的返回值
如果想得到returnd的返回值,需捕获StopIteration错误,返回值包含在StopIteration的value中。
def fib(n:int):
yield 0
if n>0:
yield 1
if n>1:
n_2,n_1 = 0,1
for _ in range(2,n+1):
n_2,n_1 = n_1,n_2+n_1
yield n_1
return 'done'
g = fib(5)
while True:
try:
x = next(g)
print(x)
except StopIteration as e:
print("return返回值:",e.value)
break
Output:
0
1
1
2
3
5
return返回值: done
生成器的执行过程、next()
与.send()
的区别
参考:
python中yield的用法详解–最简单,最清晰的解释_mieleizhi0522的博客-CSDN博客_yield
两句话总结: 区别:send
可传递参数给yield
表达式,传递的参数就会作为yield
表达式的值,也就是强行修改上一个yield
表达式值。二者返回值:当前迭代遇到的yield
时,yield
后面表达式的值。
next(generator)
或generator.__next__()
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo() # 创建一个生成器并不执行函数内的内容
print(g)
print(next(g)) # = g.__next__()
print("*"*20)
print(next(g))
Output:
<generator object foo at 0x00000163CEFD5EB0>
starting...
4
********************
res: None
4
程序运行步骤:
g = foo()
因foo函数中有yield
关键字,调用该函数会创建一个生成器对象,不会真的执行该函数,因此print(g)
的结果是:<generator object foo at 0x00000163CEFD5EB0>;- 直到调用
next()
方法,foo函数正式开始执行,先执行foo函数中的print("starting...")
得到输出:starting…; - 进入
while
循环,遇到yield 4
,在此暂停,返回值4,next(g)
语句执行完成,得到输出:4,注意这时候没有赋值给res
; - 执行
print("*"*20)
; - 执行第二个
print(next(g))
,进入foo函数,从上回暂停的位置继续执行,即执行res的赋值操作,注意这时赋值操作的右边是没有值的(因为刚才的4直接返回出去了,并没有给赋值操作的左边传参数),所以这个时候res
赋值是None
,print("res:",res)
结果为res: None; - 程序继续在
while
循环中执行,直到再一次遇到yield
,返回4,暂停,print(next(g))
的输出就是函数的返回值4。
.send()
.send()
会传入一个值作为yield
表达式整体的结果,即覆盖掉上一个yield
表达式值。
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo() # 创建一个函数并不执行
print(g)
print(next(g)) # = g.__next__()
print("*"*20)
print(g.send(7))
Output:
<generator object foo at 0x0000015AB4A55EB0>
starting...
4
********************
res: 7
4
程序运行步骤:(前4步与next()
相同)
g = foo()
因foo函数中有yield
关键字,调用该函数会创建一个生成器对象,不会真的执行该函数,因此print(g)
的结果是:<generator object foo at 0x00000163CEFD5EB0>;- 直到调用
next()
方法,foo函数正式开始执行,先执行foo函数中的print("starting...")
得到输出:starting…; - 进入
while
循环,遇到yield 4
,在此暂停,返回值4,next(g)
语句执行完成,得到输出:4,注意这时候没有赋值给res
; - 执行
print("*"*20)
; - 执行
g.send(7)
,进入foo函数,从上回暂停的位置继续执行,.send(7)
传入7覆盖掉上一次的yield
表达式,这时相当于res = 7
,print("res:",res)
结果为res: 7; - 程序继续在
while
循环中执行,直到再一次遇到yield
,返回4,暂停,print(next(g))
的输出就是函数的返回值4。
迭代器Iterator、可迭代对象Iterable:
补充一点迭代器和可迭代对象,后续写一节完整的。
- 凡是可作用于
for
循环的对象都是Iterable
类型,用isinstance()
判断一个对象是否为可Iterable; - 凡是可作用于
next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列,可用isinstance()
判断一个对象是否是Iterator对象; - 集合数据类型如
list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。