Python 生成器

Python 生成器

生成器(generator)是一种迭代器,它是生成迭代器(generator iterator)的简称。通常用来生成一个值的序列,以便在迭代中使用。另外,对于生成器的特殊语法支持使得编写一个生成器比自定义一个常规的迭代器要简单不少。从Python 2.5开始,[PEP 342:通过增强生成器实现协同程序]的实现为生成器加入了更多的特性,这意味着生成器还可以完成更多的工作。

使用生成器函数定义生成器

如果一个函数定义中使用了 yield 关键字,那么这个函数就不再是一个普通函数,而是一个生成器。如果调用该生成器函数(generator function),就会发现方法体中的代码并不会执行,相反它会返回一个生成器对象。接着,就可以利用生成器对象提供的方法控制生成器函数的执行。如下所示:

def countdown(n):
    print("Counting down from %d" % n)
    while n > 0:
        yield n
        n -= 1
    return

print(countdown)
# 输出:<function countdown at 0x02649BF0>

c = countdown(3)  # 不会执行 countdown 函数的方法体中的代码
print(c)
# 输出:<generator object countdown at 0x02B0A9B8>
print(isinstance(c, ctypes.GeneratorType))
# 输出:True

print(c.next())  # 利用生成器对象提供的方法控制生成器函数的执行
# 输出:Counting down from 3
# 输出:3
for i in countdown(10):
    print(i)

生成器对象支持的方法

生成器对象提供了 4 个方法用于控制生成器函数的执行:

  1. generator.next()
    第一次调用生成器对象的 next() 方法时,生成器才开始执行生成器函数,直至遇到 yield 语句时暂停执行(挂起),并返回 yield 关键字后面的表达式的值(如果没有则返回 None)。
    之后每次调用生成器的 next()方法,生成器将从上次暂停执行的位置恢复执行生成器函数,直至再次遇到 yield语句。
    如果当调用 next() 方法时,生成器函数执行到结束(遇到空的return语句或是到达函数体末尾)都没有遇到yield语句,则这次next方法的调用将抛出 StopIteration 异常(这标志着迭代的结束)。示例如下:

    >>> c = countdown(2)
    >>> c.next()
    Counting down from 2
    2
    >> c.next()
    1
    >> c.next()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    >>>

    通常不会在生成器对象上直接调用 next() 方法,而是将生成器对象用在 for语句、sum()或一些使用序列的其他操作中。例如:

    for n in countdown(10):
        print(n)
    
    a = sum(countdown(10))
  2. generator.send(value)
    send() 是另一个用来开始或恢复执行生成器函数的方法。与next()方法唯一不同的是,它可以给生成器函数”发送“一个值value,这个value将成为当前 yield 表达式的值。如下所示:

    def receiver():
        value = (yield 0)
        print(value)
        value = (yield 1)
        print(value)
        value = (yield 2)
        print(value)
    
    c = receiver()
    print c.send(None)  # 输出 0
    print c.send('Hello')  # 输出 Hello 1
    print c.send('World')  # 输出 World 2
    print c.send('!')  # 输出 !,并引发 StopIteration 异常

    注意,如果send() 被用来开始执行生成器函数,则只能使用 None作为参数。因为此时还没有 yield 表达式能够接收值。
    next()方法相当于send(None)

  3. generator.close()
    不再使用或删除生成器时,就会调用close()方法来关闭生成器。通常不必手动调用close()方法。对关闭的生成器再次调用next()send()将抛出 StopIteration 异常。
    在生成器函数内部,在 yield 语句上出现 GeneratorExit 异常时就会调用 close() 方法。可以选择捕获这个异常,以便执行清理操作。但是,处理异常时使用 yield 生成另一个输出值是不合法的。

    def countdown(n):
        print("Counting down from %d" % n)
        try:
            while n > 0:
                yield n
                n -= 1
        except GeneratorExit:
            print("Only made it to %d" % n)
            yield 0  # 不合法,运行时异常
        return
  4. generator.throw(type, value=None, traceback=None)
    这个方法用于在生成器函数内部(生成器函数的当前挂起处,或未启动时在定义处)抛出一个异常并恢复执行环境。例如:

    def mygen():  
        try:  
            yield 'something'  
        except ValueError: 
            print(10)
            yield 'value error'  # 注意,不会暂停
            print(20)  # 注意,不会执行
        finally:  
            print 'clean'  # 一定会被执行  
    gg=mygen()  
    print gg.next()  # something  
    print gg.throw(ValueError)  # 10 value error clean  

一个例子:把一个多维列表展开

def flatten(nested):
    try:
        #如果是字符串,那么手动抛出TypeError。
        if isinstance(nested, str):
            raise TypeError
        for sublist in nested:
            #yield flatten(sublist)
            for element in flatten(sublist):
                #yield element
                print('got:', element)
    except TypeError:
        #print('here')
        yield nested

L=['aaadf',[1,2,3],2,4,[5,[6,[8,[9]],'ddf'],7]]
for num in flatten(L):
    print(num)

生成器表达式

生成器表达式的语法与列表推导的语法类似,只需将[]替换成()

(expression for item1 in iterable1 if condition1
            for item2 in iterable2 if condition2
            ...
            for itemN in iterableN if conditionN)

与列表推导直接创建一个列表不同,生成器表达式实际上并不会创建一个包含结果数据的列表,也不会立即对圆括号内的表达式求值。相反,生成器表达式会创建一个生成器对象。在某些应用中,这个差异可能会极大地影响性能和内存使用。

list = [i for i in xrange(10) if i % 2 == 0]
print list
# 输出:[0, 2, 4, 6, 8]

generator = (i for i in xrange(10) if i % 2 == 0)
print isinstance(generator, types.GeneratorType)
# 输出:True
print generator.next()
# 输出:0

使用内置的list()函数可以将生成器对象转成列表:

generator = (i for i in xrange(10) if i % 2 == 0)
print list(generator)
# 输出:0, 2, 4, 6, 8

补充阅读:一个有趣的库 pipe

pipe 并不是 Python 内置的库,你可以使用 easy_install pipe 直接安装它,或者从这里下载源码进行安装。

之所以要介绍这个库,是因为它向我们展示了一种很有新意的使用迭代器和生成器的方式:流。pipe将可迭代的数据看成是流。类似于linux,pipe使用 ’|‘ 传递数据流,并且定义了一系列的“流处理”函数用于接受并处理数据流,并最终再次输出数据流或者是将数据流归纳得到一个结果。

我们来看一些例子:

  1. 使用 add 求和:

    >>> from pipe import *
    >>> range(5) | add
    10
  2. 使用 where 过滤符合条件的元素:

    >>> range(5) | where(lambda x: x % 2 == 0) | add
    6
  3. 使用 take_while 截取元素直到条件不成立(与 itertools 模块的同名函数功能类似):

    from pipe import *
    
    def fibonacci():
        a = b = 1
        yield a
        yield b
        while True:
            a, b = b, a+b
            yield b
    
    print(fibonacci() | where(lambda x: x % 2 == 0) | take_while(lambda x: x < 10000) | add)
    
    # 输出:3382
    
  4. 需要对元素应用某个函数可以使用 select (作用类似于内建函数 map)。需要得到一个列表,可以使用as_list

    >>> fibonacci() | select(lambda x: x**2) | take_while(lambda x: x < 100) |  as_list
    [1, 1, 4, 9, 25, 64]
  5. 可以自己定义流处理函数,只需要定义一个生成器函数并加上装饰器 Pipe。如下定义了一个获取元素直到索引不符合条件的流处理函数:

    @Pipe
    def take_while_idx(iterable, predicate):
        for idx, x in enumerate(iterable):
            if predicate(idx):
                yield x
            else:
                return
    
    print fibonacci() | take_while_idx(lambda x: x < 10) | as_list
    
    #输出:[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
    

pipe实现起来非常简单,使用 Pipe 装饰器,将普通的生成器函数(或者返回迭代器的函数)代理在一个实现了 __ror__方法的普通类实例上即可,但是这种思路真的很有趣。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值