【Python】生成器(generator)、yield含义

如有问题欢迎指出!

定义

Python中生成器是迭代器(Iterator) 的一种,每次遇到 yield时函数会暂停并保存当前所有的运行信息,返回 yield 后的值, 并在下一次执行 next()方法时从当前位置继续运行。

列表一次生成一组值,占用内存空间;生成器一次只产生一个值,用多少,取多少。

创建生成器(Generator)

如何创建生成器?

  1. 将列表生成式的中括号[]改为小括号(),得到生成器表达式
  2. 定义一个包含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]
 at 0x000001F4C81E1AC0>

注意:调用生成器函数时generator2 =generator_2(3)只是创建了一个生成器,并不会执行函数里面的内容

获得生成器的值

  1. 重复调用next() 方法 ,直到捕获一个异常;
  2. 通过 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:


0
1
print(next(generator1))
print(next(generator1))
print(next(generator1))
print(next(generator1))

Output:

【Python】生成器(generator)、yield含义_第1张图片

通过重复调用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(n1)+fib(n2)

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:


starting...
4
********************
res: None
4

程序运行步骤:

  1. g = foo()因foo函数中有yield关键字,调用该函数会创建一个生成器对象,不会真的执行该函数,因此print(g)的结果是:
  2. 直到调用next()方法,foo函数正式开始执行,先执行foo函数中的 print("starting...")得到输出:starting…;
  3. 进入while循环,遇到yield 4 ,在此暂停,返回值4,next(g)语句执行完成,得到输出:4,注意这时候没有赋值给res
  4. 执行print("*"*20);
  5. 执行第二个print(next(g)),进入foo函数,从上回暂停的位置继续执行,即执行res的赋值操作,注意这时赋值操作的右边是没有值的(因为刚才的4直接返回出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是Noneprint("res:",res)结果为res: None;
  6. 程序继续在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:


starting...
4
********************
res: 7
4

程序运行步骤:(前4步与next()相同)

  1. g = foo()因foo函数中有yield关键字,调用该函数会创建一个生成器对象,不会真的执行该函数,因此print(g)的结果是:
  2. 直到调用next()方法,foo函数正式开始执行,先执行foo函数中的 print("starting...")得到输出:starting…;
  3. 进入while循环,遇到yield 4 ,在此暂停,返回值4,next(g)语句执行完成,得到输出:4,注意这时候没有赋值给res
  4. 执行print("*"*20);
  5. 执行g.send(7),进入foo函数,从上回暂停的位置继续执行,.send(7)传入7覆盖掉上一次的yield 表达式,这时相当于res = 7print("res:",res)结果为res: 7;
  6. 程序继续在while循环中执行,直到再一次遇到yield,返回4,暂停,print(next(g))的输出就是函数的返回值4。

迭代器Iterator、可迭代对象Iterable:

补充一点迭代器和可迭代对象,后续写一节完整的。

  • 凡是可作用于for循环的对象都是Iterable类型,用isinstance()判断一个对象是否为可Iterable
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列,可用isinstance()判断一个对象是否是Iterator对象;
  • 集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

你可能感兴趣的:(Python基础,python)