python 生成器

生成器

​ 利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。

创建生成器方法1

要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )

a = [i ** i for i in range(5)]
b = (i ** i for i in range(5))
print(a)
print("*" * 50)
print(b)

创建 L 和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是一个列表,而 G 是一个生成器。我们可以直接打印出列表L的每一个元素,而对于生成器G,我们可以按照迭代器的使用方法来使用,即可以通过next()函数、for循环、list()等方法使用。

创建生成器方法2

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。

斐波那契数列来举例:用迭代器实现代码:

class FibIterator(object):
    """斐波那契数列迭代器"""
    def __init__(self, n):
        """
        :param n: int, 指明生成数列的前n个数
        """
        self.n = n
        # current用来保存当前生成到数列中的第几个数了
        self.current = 0
        # num1用来保存前前一个数,初始值为数列中的第一个数0
        self.num1 = 0
        # num2用来保存前一个数,初始值为数列中的第二个数1
        self.num2 = 1
 
    def __next__(self):
        """被next()函数调用来获取下一个数"""
        if self.current < self.n:
            num = self.num1
            self.num1, self.num2 = self.num2, self.num1+self.num2
            self.current += 1
            return num
        else:
            raise StopIteration
 
    def __iter__(self):
        """迭代器的__iter__返回自身即可"""
        return self

用生成器实现代码:

In [30]: def fib(n):
   ....:     current = 0
   ....:     num1, num2 = 0, 1
   ....:     while current < n:
   ....:         num = num1
   ....:         num1, num2 = num2, num1+num2
   ....:         current += 1
   ....:         yield num
   ....:     return 'done'

在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。简单来说:只要在def中有yield关键字的 就称为 生成器

此时按照调用函数的方式( 案例中为F = fib(5) )使用生成器就不再是执行函数体了,而是会返回一个生成器对象( 案例中为F ),然后就可以按照使用迭代器的方式来使用生成器了。

yield

#参考了 https://blog.csdn.net/mieleizhi0522/article/details/82142856

看懂下面的代码就能知道这是什么了:

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
 starting...
 4
 ********************
 res: None
 4

解释:

1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

3.程序遇到yield关键字,把yield想象成return,return了一个4之后,程序已经停止,并没有执行赋值给res的操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,

4.程序执行print("*"*20),输出20个*

5.又开始执行下面的print(next(g)),这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,但是这个时候赋值操作的右边是没有值的(因为4已经被return出去了),所以res赋值结果是None,输出了res:None,

6.程序会继续在while里执行,又一次碰到yield,这个时候同样return出4,然后程序停止,print函数输出的4就是这次return出的4。

带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

生成器的close方法

1.对已经关闭的生成器对象使用 next 会抛出 StopIteration 异常。

def gen():
    print('下面 yield 1')
    yield 1
    print('下面 yield 2')
    yield 2

g = gen()
print(next(g))
g.close()
print()
next(g)
下面 yield 1
1

  File "   ", line 53, in <module>
    next(g)
StopIteration

2.生成器自然退出抛出 StopIteration 异常,该异常不会传递给调用方,close 方法正常返回。

def gen():
    try:
        yield 1
    except GeneratorExit:
        print('捕获到GeneratorExit')
    print('生成器函数结束了')

g = gen()
print(next(g))
g.close()


#输出:
"""
1
捕获到GeneratorExit
生成器函数结束了
"""

3.在 GeneratorExit 抛出后还有 yield 语句,会产生 RuntimeError。另外生成器对象被垃圾回收时,解释器会自动调用该对象的 close 方法(PEP 342),这意味着最好不要在相应的 except 和 finally 中写 yield 语句,否则不知道什么时候就会抛出 RuntimeError 异常。

def gen():
    try:
        yield 1
    except GeneratorExit:
        print('捕获到 GeneratorExit')
        print('尝试在 GeneratorExit 产生后 yield 一个值')
        yield 2

    print('生成器结束')


g = gen()
next(g)
g.close()
"""
捕获到 GeneratorExit
尝试在 GeneratorExit 产生后 yield 一个值
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    g.close()
RuntimeError: generator ignored GeneratorExit
"""

生成器的send方法

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(g.send(7))

#输出结果
    starting...
    4
    ********************
    res: 7
    4

​ send是发送一个参数给res的,因为上面讲到,return的时候,并没有把4赋值给res,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用send的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了res,然后执行next的作用,遇见下一回的yield,return出结果后结束。

注意:

  • 如果生成器未启动,则必须在使用send()前必须要启动生成器,而启动的方法可以是 generator.next() 或是 generator.send(None) 执行到第一个yield处。之后就可以使用 send(params) 不断传入值了。如果是已启动,则 send(params) 的作用就是对xx赋值为发送的值(send的参数),然后让生成器执行到下个yield…
  • 为什么需要 send(None) ,也很好理解,因为生成器还没有走到第一个 yield 语句,如果我们发生一个真实的值,这时是没有人去“接收”它的。一旦生成器启动了,就对象接受(即=号左边的左值xx接受了),之后就可以使用 send(params) 不断传入值了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值