Python的生成器

来源:互联网 发布:网络主播当红10首歌曲 编辑:程序博客网 时间:2024/06/05 16:57

生成器是Python新引入的概念,由于历史原因,它也叫做简单生成器。它和迭代器可能近几年来引入的最大的两个特征。但是,生成器的概念则要更高级一些,需要花费一些功夫才能理解它是如何工作的以及有什么用处。生成器可以帮助读者写出优雅的代码,当然,编写任何程序时不使用生成器也是可以的。

生成器是一种普通的函数语法定义的迭代器。它的工作方式可以用例子来很好地展现。

1、创建迭代器

创建一个生成器就象创建函数一样。接下来用一个例子说明生成器的知识。首先创建一个嵌套列表的函数。参数是一个例子,如下所示:

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

换句话说技术第一个列表的列表。函数应该按顺序打印列表中的数字。解决的方法如下:
def flattern(nested):for sublist in nested:for element in sublist:yield element                print(element)

这个函数的大部分是很简单的。首先迭代提供的嵌套列表中的所有子列表,然后按顺序迭代子列表中的元素。如果最后一行是print(element)的话,那么就容易理解了,不是吗?
这里的yield语句是新知识。任何包含yield语句的函数称为生成器。除了名字不同以外,它的行为和普通的函数也有很大的差别。这就在于它不像return哪样返回值,而是每次产生多个值。每次产生一个值,函数就会被冻结:即函数停在哪点等待被重新唤醒。函数被重新唤醒后就从停止的哪点开始执行。





2、递归生成器

上诉创建的生成器只能处理两层嵌套,为了处理嵌套使用了两个for循环。哪如何处理任意层嵌套的呢?每层嵌套需要增加一个for循环,但因为不知道几层嵌套,所以必须把解决方案变得灵活。用递归能解决此问题。

def flatten(nested):try:for sublist in nested:for element in flatten(sublist):yield elementexcept TypeError:yield nested


当flattern被调用时,有两种可能性:基本情况和需要递归的情况。

在基本情况中,函数被告知展开一个元素,这种情况下,for循环会引起一个TypeError异常,生成器会产生一个元素。

需要递归情况中,展开还是一个列表。程序会遍历所有的字列表,并对他们调用flattern,然后使用使用另一个for循环来产生被展开的字列表中的所有元素。

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

[1,2,3,4,5,6,7,8]

如果输入的函数类似于字符串的对象,迭代对结果产生干扰(但不会引发TypeError),为了处理这种情况,则必须在生成器中的开始添加一个检查语句。试着将传入的对象和一个字符拼接,看看会不会引发TypeError,这是检查一个对象是不是类似于字符串的最简单,最快捷的方法。下面是加入了检查语句的生成器:

def flatten(nested):try:try:nested+' 'except TypeError:passelse:raise TypeErrorfor sublist in nested:for element in flatten(sublist):yield elementexcept TypeError:yield nested

如果表达式nest+‘ ’引发了一个TypeError,它就会被忽略。然后如果没有引发TypeError,那么内层try语句中else子句就会引发一个TypeError异常。这就会按照原来的样子生成类似于字符串的对象(在except子句的外面).

例子展示:

>>>list(flatten(['foo',['bar',['baz']]]))

['foo','bar','baz']



3、通用生成器

如果到目前的所有例子你看懂了,哪应该或多或少地知道如何使用生气器。生成器是一个包含yield关键字的函数。当它被调用时,在函数体中的代码,而会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,知道遇见一个yield或者return语句。yield语句意味着应该生成一个值。return语句意味着生成器要停止执行(不再生成任何东西,return语句只有一个生成器中使用时才能进行无参数调用)。

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

>>>def simple_generator():

yield 1

. . .

>>>simple_generator

<function simple_generator at 153b44>

>>>simple_generator()

<genertor object at 1510b0>

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


4、生成器方法

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

(1)外部作用域访问生成器的send方法,就像访问next方法一样,只不过前者使用一个参数(要发送的‘’消息‘’————任意对象)。

(2)在内部则挂起生成器,yield现在作为表达式而不是语句使用,换句话说,当生成器重新运行的时候,yield方法返回一个值,也就是外部通过send发放发送的值。如果next方法被使用,那么yield方法返回None。

!!!使用send方法只有在生成器挂起之后才有意义(也就是说yield函数第一次被执行之后)。如果在此之前需要给生成器提供更多信息,那么只需要使用生成器函数的参数。如果真想对刚刚启动的生成器使用send方法,那么可以将None作为其参数进行调用。

例:

def repeater(value):while True:new=(yield value)if new is not None:value=new


使用方法如下:

r=repeater(42)

r.next()

42

r.send("Hello,world!")

"Hello,world!"

生成器还有其他两个方法:

(3)throw方法用于在生成器内引发一个异常

(4)close方法用于停止生成器。

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


5、模拟生成器

生成器在旧版本中的python中是不可用的。下面介绍的使用普通函数模拟生成器。

先从生成器的代码开始。首先下面语句放在函数图的开始处:

resulr=[ ]

然后将下面这种形式的代码:

yield some_expression

用下面的语句替换:

result.append(some_expression)

最后,在函数的末尾,添加下面的语句:

return result

此方法适用于大多数的生成器(比如,不能用于一个无限的生成器,当然不能把它的值放入列表中).

下面是flattern生成器用普通函数的版本:

def flatten(nested):result=[]try:try:nested+' 'except TypeError:passelse:raise TypeErrorfor sublist in nested:for element in flatten(sublist):result.append(element)except TypeError:result.append(nested)return result



0 0