Python之生成器

 

生成器是迭代器的一种,Python中有两种方法来实现生成器,一个是生成器函数,一个是生成器表达式。

       生成器函数,带yield的函数:

def func(n):
    for i in range(n):
        yield i + 1

a = func(3)
print(a)   #output: <generator object func at 0x7fa999bef890>
print(iter(a) is a) #output: True
print(next(a))   #output: 1
print(next(a))   #output: 2
print(next(a))   #output: 3
print(next(a))   #output: StopIteration

      生成器表达式:

           用列表推导(生成式)来初始化一个列表:

list5 = [x for x in range(5)]
print(list5)   #output:[0, 1, 2, 3, 4]

          将列表推导式的[] 换成圆括号就是生成器表达式

gen = (x for x in range(5))
print(gen) 
#output: <generator object <genexpr> at 0x0000000000AA20F8>

          看到上面print(gen) 并不是直接输出结果,而是告诉我们这是一个生成器。那么我们要怎么调用这个gen呢?

          有两种方式: 

          第一种:

for item in gen:
    print(item)
#output:
0
1
2
3
4

         第二种:

print(next(gen))#output:0
print(next(gen))#output:1
print(next(gen))#output:2
print(next(gen))#output:3
print(next(gen))#output:4
print(next(gen))#output:Traceback (most recent call last):StopIteration

       若在生成器表达式左侧的变量相关处理过程中存在异常,则该异常不会在表达式定义阶段被触发,其会在第一次求值时触发;若在生成器表达式右侧的 for 子句中存在异常,则在表达式定义阶段该异常会被触发。

a = (i*2 for i in [None])
a.__next__()  #output TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'

 

       简要的说,在函数定义中使用了 yield 表达式的函数可称为生成器函数( generator )。生成器表达式可以认为是一种特殊的生成器函数,类似于lambda表达式和普通函数。相对于一般函数用return来一次性返回所有值,生成器使用yield关键字,一次只返回一个值。

      这样的设计有很大的好处:在数据处理时,如果函数return出来的是一个非常大的数组,那么会非常占用内存,有时会报MemoryError的错误,而使用yield后一次仅仅返回一个元素值,可以优化内存占用的情况。

        生成器函数在被调用时,其返回的对象是一个迭代器(其支持 iter() 和 next() 调用,满足迭代器协议),我们把这个返回的对象称为生成器迭代器( generator iterator )。从这个角度来看,生成器函数也是一种实现迭代器协议的方式,除了上面所示的直接编写为生成器函数以外,还可以在类的 __iter__ 方法中使用 yield 表达式。

      在生成器函数创建生成器迭代器的过程中,其会在内部自动的支持迭代器协议,所以也可以在 for 循环、map() 等迭代工具中直接使用生成器迭代器。

 

      生成器函数的挂起与恢复

      和普通函数被调用后直接生成结果不同的是,生成器函数的执行是由生成器函数返回的生成器迭代器进行控制的,比如我们在上面的示例中通过 next() 函数隐式调用生成器迭代器的 __next__ 方法可以让生成器函数开始执行,或者让生成器函数从上次执行 yield 的位置恢复之前的状态继续执行。当生成器函数开始执行后,直至下一个 yield 表达式处,函数会暂停处理被挂起,并向调用者产出一个值。生成器函数挂起时,其局部状态都会被保存下来,包括局部作用域、代码位置、异常处理状态等,这些局部状态的保存使得生成器函数再次执行时可以恢复到可执行的状态,以保证从上次挂起的位置处继续执行。

 

     生成器函数的优势

        对于一个普通的函数来说,其结果的计算是一次完成的,当结果是包含大量数据的序列或其他容器时,会导致程序可能会在内存方面存在低效问题。

        不过对于生成器函数来说,这并不是一个问题,生成器函数不会一次性生成所有结果。生成器中每一个结果的计算穿插在整个迭代过程中。同时生成器函数在每次迭代之间会自动的将局部状态进行保存并在下一次迭代中自动恢复,这有利于一些需要手动保存局部状态的程序的编写。

 

  生成器迭代器的方法

          除了我们在上面提到的 generator.__next__() 方法外,生成器迭代器还有 generator.send() 、generator.throw()、generator.close() 等方法。

          generator.__next__() :在生成器迭代器中可以通过 for 循环等迭代工具或 next() 函数被隐式的调用,用来开始一个生成器函数的执行或恢复生成器函数的执行。

          generator.send():恢复生成器函数的执行并向生成器函数发送一个 value 值,使得在调用者和生成器之间可以进行通信。

def func(n):
    for i in range(n):
        m = [(yield i + 1)] * 2
        print(m)


a = func(3)
print(next(a))  #output 1
a.send(10)   #output [10, 10]
next(a)      #output [None, None]
a.send(20)   #output [20, 20]  StopIteration

         上面这段代码包含三个重要的知识点,我们逐点来进行讲解:

  • 首先,yield 表达式需要被包含在括号中,当其是赋值语句右侧的唯一项时,括号可以省略。其他情况下括号则不能被省略,比如这样的写法 m = [yield i + 1] * 2 是错误的,会引发 SyntaxError 异常。

  • 其次,在首次向生成器函数发送一个非 None 的 value 值之前,我们需要启动生成器(也称为生成器预激)。预激的作用是让生成器启动并执行到第一个 yield 表达式处并暂停。在生成器没有启动时就发送数据会引发 TypeError: can't send non-None value to a just-started generator ,这是因为此时并没有可以接收值的 yield 表达式。

    我们可以使用 next() 来启动生成器,除此之外,我们还可以使用 send(None) 来启动生成器,两者的效果是相同的。

  • 最后,我们应该合理的区分生成器产出的值和 yield 表达式的返回值。可以看到上面代码的运行结果中,value 通过 a.send(value) 被发送给生成器函数,生成器函数在恢复执行的同时,value 值也成为了 yield 表达式的返回值。

    同时也可以看到,当生成器函数通过 next() 方法恢复执行时 ,yield 表达式的返回值为 None 。

             generator.throw(type[, value[, traceback]]) :在生成器函数暂停的位置引发 type 类型的异常,并将下一个要产出的值返回。当生成器函数没有捕获该 type 类型的异常时,则异常会被传递到调用方。

def func(n):
    for i in range(n):
        try:
            m = [(yield i + 1)] * 2
            print(m)
        except Exception:
            print('Exception  handled')

a = func(3)
print(next(a))  #output  1
b = a.throw(Exception)  #output Exception  handled
print(b)   #output 2

 

  yiled from: 委托给子生成器

      在 Python 3.3 中,PEP 380 – Syntax for Delegating to a Subgenerator 引入了 yield from 表达式,允许生成器将其部分处理过程委托给另一个生成器,这样一段包含有 yield 表达式的代码可以被分解成一个子生成器,该子生成器可以返回一个值,该值可以被外部生成器使用。 

       yiled from的详解,可以参考这两篇文章:《python协程系列(三)——yield from原理详解》  《 为什么要使用yield from

 

 

      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X-Programer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值