注册 登录

清河洛

python中的生成器及yield

qingheluo2017-09-16清河洛306
在实际开发中受到内存限制,列表容量肯定是有限的而且,创建一个包含太多个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间在Python中,这种一边循环一边计算的机制,称为生成器:generator要创建一个generator,有三种方法方法一、只要把一个列表生成式的[]改成():>>>g=(x*x for x in range(10)) >>>g &...

在实际开发中受到内存限制,列表容量肯定是有限的

而且,创建一个包含太多个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间

在Python中,这种一边循环一边计算的机制,称为生成器:generator

要创建一个generator,有三种方法

方法一、只要把一个列表生成式的[]改成():

>>>g=(x*x for x in range(10))
>>>g
<generatorobject<genexpr>at0x1022ef630>

方法二、iter()函数用于将可迭代对象如list、tuple、dict、set、str等转换为生成器对象

方法三、如果推算的算法比较复杂,用类似列表生成式无法实现的时候,可以在实现算法的函数中使用yield表达式来实现

一个带有yield表达式的函数就是一个生成器generator,当调用这个函数时不会执行任何函数代码只返回一个生成器对象

yield是一个表达式,是表达式就有返回值,yield关键字后面的值是返回给调用者的值而并不是表达式的返回值,表达式的返回值需要我们主动传入

当生成器执行到yield表达式时会返回yield后面的值并被动的等待我们传入yield表达式的值(对外表现为中断),当传入yield表达式的值后程序会向下继续运行

生成器对象的send()与next()方法用于传递yield表达是的值,区别在于send传递的值可以由我们指定,而next只会传递None值,所以send(None)等同于next()

生成器使用特点

第一次运行生成器对象时因为没有yield表达式运行过,所以没有赋值的目标,此时必须传入None值,也就是第一次运行时只能使用send(None)或next()

一般第一次运行next()或者send(None)我们称之为激活生成器

一个generator执行结束会抛出StopIteration(expr)异常,其中expr为return的值,该错误对象的value属性为expr值

在for循环中会自动调用next()并返回生成器的返回值,直到遇到StopIteration错误时结束循环

def demo(msg):
    print("starting...,msg:"+msg)
    while True:
        val=yield msg
        print(f"msg:{msg},val:{val}")
        if val=='exit':
            return "end"

test=demo('one')
    #函数中有yield关键字,所以函数并不会真的执行,而是得到一个生成器对象并赋值给变量test
next(test)
    #运行next()运行函数体并运行至yield处中断,此时程序已经输出"start,msg:one"
    #此时已经运算完成yield表达式,生成器返回msg的值"one"
val=test.send('two')
    #运行send()传入值"two",此时变量val赋值为send()传入值"two"
    #继续向下运行输出:"msg:one,val:two"
    #继续向下运行知道下次循环再次遇到yield关键字
print('生成器返回值:'+val)
    #生成器返回值为msg变量的值
try:
    test.send('exit')
except StopIteration as e:
    print('返回值:',e.value)

#继续向下运行程序直到再次遇到yield关键字重复以上步骤或遇到return语句结束

综合以上代码的运行结果为

starting...,msg:one
msg:one,val:two
生成器返回值:one
msg:one,val:exit
返回值:end

上例中我们认识了生成器的next()和send()两个方法,生成器还有两个重要的方法:close()和throw()方法

close()方法用于手动关闭生成器,后续调用会直接返回StopIteration异常

生成器的throw(error)方法,用于在生成器中抛出指定异常,如果在生成器中捕获了该异常,throw方法会返回下一次迭代值,否则throw方法返回指定的异常

def my_gen():
    yield "a"
    yield "b"
g=my_gen()
print(next(g)) # a
print(g.throw(ValueError("throw方法抛出的异常")))
    #抛出ValueError异常
    #使用了g.throw(ValueError),而生成器中并没有捕获ValueError异常,所以生成器结束运行,throw方法返回该异常

defmy_gen():
    try:
        yield"a"
        yield"b"
    except ValueError as e:
        print("生成器中捕获了该异常")
g=my_gen()
print(next(g)) # a
print(g.throw(ValueError("throw方法抛出的异常")))
    #抛出StopIteration异
    #由于生成器中捕获了指定异常,生成器会继续执行,但是由于当前生成器已经执行到最后,所以会抛出StopIteration异常

defmy_gen():
    whileTrue:
        try:
            yield"a"
            yield"b"
        except ValueError as e:
            print("生成器中捕获了该异常")
g=my_gen()
print(next(g)) # a
print(g.throw(ValueError("throw方法抛出的异常"))) # a
    # 生成器捕获了指定异常,生成器继续执行,在yield"a"表达式处暂停并返回"a",此时throw方法的返回值为"a"

生成器的GeneratorExit异常

当一个生成器被销毁时会抛出GeneratorExit异常后再销毁,也就是说当一个生成器执行完毕或通过del语句或调用close()主动销毁生成器对象时会抛出该异常

GeneratorExit异常只有在生成器对象被激活后,才有可能产生,未激活时即使使用del语句主动销毁也不会抛出GeneratorExit异常

defgen():
    try:
        yield"a"
        yield"b"
    except GeneratorExit:
        print('生成器销毁了')
g=gen()
next(g)
del g
#打印"生成器销毁了"
显式的捕获了GeneratorExit异常,如果该异常没有被显式捕获,生成器对象也不会把该异常向主程序抛出

因为GeneratorExit异常定义的初衷,是方便开发者在生成器对象调用结束后定义一些收尾的工作,如释放资源等

迭代器

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等

一类是生成器generator

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。

可以使用isinstance()判断一个对象是否是Iterable对象

from collections import Iterable
isinstance([],Iterable)

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

可以使用isinstance()判断一个对象是否是Iterator对象

from collections import Iterator
isinstance((xforxinrange(10)),Iterator)

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator

yield from表达式

yield from是在Python3.3才出现的语法,后面是可迭代对象Iterable

在使用yield表达式的时候,使用for语句迭代是自动捕获StopIteration,如果遇到return只是会终止迭代,没办法获取最后return的值

yield from也是一个表达式,那么也有返回值,该返回值就是最后的return值,我们可以获取该return值

def my_gen():
    for i in range(5):
        if i==2:
        return '中断了'
        yield i

def wrap_gen(gen):
    res=yield from gen
    yield res

def main(gen):
    for j in gen:
        print(j)

wrap_g=wrap_gen(my_gen())
main(wrap_g)

以上代码会输出
0
1
中断了
其中最后一行的"中断了"就是wrap_gen中的yieldres表达式返回值,也就是my_gen中的return值

在PEP 380使用了一些专门术语

委派生成器:包含yield from 表达式的生成器函数,即示例中wrap_gen生成器函数

子生成器:从yield from表达式中部分获取的生成器;即示例中my_gene生成器函数

调用方:调用委派生成器的代码;即示例中main函数

委派生成器会在yield from表达式处暂停,此时调用方可以直接把数据发给子生成器,子生成器的返回值也是直接返回给调用方

委派生成器相当于在调用方与子生成器之间建立一个双向通道,它本身并没有权利也没有办法对子生成器yield回来的内容做拦截

当子生成器抛出异常时(如运行结束抛出StopIteration异常或者其他情况下抛出其他异常)并不会抛出给调用方,而是抛给委派生成器,此时委派生成器会恢复执行,并捕获异常中的第一个参数值赋值给yield from表达式

yield from x 表达式内部首先是调用iter(x),然后再调用next(),因此x是任何的可迭代对象

可以把任意数量的委派生成器连接在一起,一个委派生成器使用yield from调用一个子生成器,而子生成器本身也可以是委派生成器,使用yield from调用另一个生成器



网址导航