Bootstrap

Python 生成器

生成器是什么?

生成器(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

;