[python]生成器

生成器是python新引入的概念,由于历史原因,它也叫简单生成器。它和迭代器可能是近两年来引入的最强大的两个特性。但是,生成器的概念则要更高级一些。生成器可以帮助程序员写出非常优雅的代码。

生成器是一种用普通的函数语法定义的迭代器。它的工作方式可以用例子来很好的展现,我们先看看怎么创建和使用生成器,然后再了解一下它的内部机制。

创建生成器

创建生成器就像创建函数一样简单。首先我们创建一个展开嵌套列表的函数,其参数是一个列表,列表的一个例子如下

>>> nested = [[1,2,3], [4, 5], [6]]

也就是说,这是一个列表的列表。函数应该按照顺序打印出列表中的数字。定义的函数如下:

>>> def flatten(nested):
...     for sublist in nested:
...         for element in sublist:
...             yield element
... 
>>> 

这个函数很容易理解。首先迭代提供的嵌套列表中的所有子列表,然后按照顺序迭代子列表的元素。如果最后一行是print element的话,那么就更容易理解了。

这里的yield语句是我们需要理解的知识点。任何包含yield语句的函数称为生成器。除了名字不同以外,它的行为和普通的函数也有很大的差别。这就在于它不像return那样返回值,而是每次产生多个值。每次产生一个值(使用yield语句),函数就会被冻结:即函数停在那里等待被激活,函数被激活后就从停止的那点开始继续执行。

接下来可以通过在生成器上来迭代使用所有的值。

>>> for num in flatten(nested):
...     print num
... 
1
2
3
4
5
6
>>> flatten(nested)
<generator object flatten at 0x7f812746b910>
>>> list(flatten(nested))
[1, 2, 3, 4, 5, 6]

递归生成器

上面创建的生成器只能处理两层嵌套,为了处理嵌套使用了两个for循环。如果要处理任意层的嵌套该怎么办?如果每层嵌套需要增加一个for循环,但是不知道有几层嵌套,就无法写出具体的代码。这时候,就必须求助于递归了。

>>> def flatten(nested):
...     try:
...         for sublist in nested:
...             for element in flatten(sublist):
...                 yield element
...     except TypeError:
...         yield nested
... 
>>> 

当flatten被调用时,有两种可能性:基本情况和需要递归的情况。在基本情况中, 函数被告知展开一个元素,这时候,for循环会引发一个TypeError异常,生成器会产生一个元素。

如果展开的是一个列表,那么就要进行特殊处理。程序必须遍历所有的子列表,并对它们调用flatten。然后使用另一个for循环来产生被展开的子列表的所有元素。这可能看起来有点不可思议,但却能工作。

>>> list(flatten([[[1], 2], 3, 4, [5, [6, 7], 8]]))
[1, 2, 3, 4, 5, 6, 7, 8] 

通用生成器

生成器是一个包含yield关键字的函数。当它被调用时,在函数体中的代码不会被执行,而会返回一个迭代器。每次请求一个值, 就会执行生成器中的代码,直到遇到一个yield或者return语句。yield语句意味着应该生成一个值。return语句意味着生成器要停止执行。

换句话说,生成器是由两部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义的,包含了yield的部分,生成器的迭代器是这个函数的返回的部分。按一种不是很准确的说法,两个实体经常被当做一个,合起来叫做生成器。比如:

>>> def simple_generator():
...     yield 1
... 
>>> simple_generator
<function simple_generator at 0x7f81274805f0>
>>> simple_generator()
<generator object simple_generator at 0x7f812746b9b0>

生成器的函数返回迭代器可以像其他的迭代器那样使用

生成器方法

生成器的新属性是在开始运行后为生成器提供值的能力。表现为生成器和“外部世界”进行交流的渠道,要注意下面两点。

  • 外表作用域访问生成器的send方法,就像访问next方法一样,只不过前者使用一个参数。
  • 在内部则挂起生成器,yield现在作为表达式而不是语句使用,换句话说,当生成器重新运行的时候,yield方法返回一个值,也就是外部通过send方法发送的值。如果next方法被使用, 那么yield方法返回None。

注意, 使用send方法只有在生成器挂起之后才有意义,也就是说在yield函数第一次被执行之后。如果在此之前需要给生成器提供更多信息,那么只需使用生成器函数的参数。

生成器还有其他两个方法

  • throw方法,用于在生成器内引发一个异常
  • close方法,用于停止生成器,调用时不需要参数

其中close方法也是建立在异常的基础上的。它在yield运行出引发一个GeneratorExit异常,所有如果需要在生成器内进行代码清理的话,则可以将yield语句放在try/finally语句中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值