C++,python,热爱算法和机器学习
全部博文(1214)
分类: Python/Ruby
2011-12-23 10:01:34
4. 生成器(generator) 4.1. 生成器简介生成器是迭代器,同时也并不仅仅是迭代器,不过迭代器之外的用途实在是不多,所以我们可以大声地说:生成器提供了非常方便的自定义迭代器的途径。
这是函数式编程指南的最后一篇,似乎拖了一个星期才写好,嗯……
转载请注明原作者和原文地址:)
首先请确信,生成器就是一种迭代器。生成器拥有next方法并且行为与迭代器完全相同,这意味着生成器也可以用于Python的for循环中。另外,对于生成器的特殊语法支持使得编写一个生成器比自定义一个常规的迭代器要简单不少,所以生成器也是最常用到的特性之一。
从Python 2.5开始,[PEP 342:通过增强生成器实现协同程序]的实现为生成器加入了更多的特性,这意味着生成器还可以完成更多的工作。这部分我们会在稍后的部分介绍。
4.2. 生成器函数 4.2.1. 使用生成器函数定义生成器如何获取一个生成器?首先来看一小段代码:
1 2 3 4 5 6 7 | >>> def get_0_1_2(): ... yield 0 ... yield 1 ... yield 2 ... >>> get_0_1_2 |
我们定义了一个函数get_0_1_2,并且可以查看到这确实是函数类型。但与一般的函数不同的是,get_0_1_2的函数体内使用了关键字yield,这使得get_0_1_2成为了一个生成器函数。生成器函数的特性如下:
1 2 3 | >>> generator = get_0_1_2() >>> generator |
1 2 | >>> generator.next() 0 |
1 2 3 4 | >>> generator.next() 1 >>> generator.next() 2 |
1 2 3 4 | >>> generator.next() Traceback (most recent call last): File " StopIteration |
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> def fibonacci(): ... a = b = 1 ... yield a ... yield b ... while True: ... a, b = b, a+b ... yield b ... >>> for num in fibonacci(): ... if num > 100: break ... print num, ... 1 1 2 3 5 8 13 21 34 55 89 |
接下来我们来讨论一些关于生成器的有意思的话题。
1 2 3 4 5 | >>> def counter(start=0): ... while True: ... yield start ... start += 1 ... |
1 2 3 4 5 6 | >>> def i_wanna_return(): ... yield None ... return None ... File " SyntaxError: 'return' with argument inside generator |
1 2 3 4 5 6 7 8 9 10 11 | >>> def play_u(): ... try: ... yield 1 ... yield 2 ... yield 3 ... finally: ... yield 0 ... >>> for val in play_u(): print val, ... 1 2 3 0 |
1 2 3 4 5 | >>> def sub_generator(): ... yield 1 ... yield 2 ... for val in counter(10): yield val ... |
1 2 3 4 5 6 7 8 | >>> def sub_generator(): ... yield 1 ... yield 2 ... yield from counter(10) File " yield from counter(10) ^ SyntaxError: invalid syntax |
有更多问题?请回复此文:)
4.3. 协同程序(coroutine)协同程序(协程)一般来说是指这样的函数:
协程的特点决定了同一时刻只能有一个协同程序正在运行(忽略多线程的情况)。得益于此,协程间可以直接传递对象而不需要考虑资源锁、或是直接唤醒其他协程而不需要主动休眠,就像是内置了锁的线程。在符合协程特点的应用场景,使用协程无疑比使用线程要更方便。
从另一方面说,协程无法并发其实也将它的应用场景限制在了一个很狭窄的范围,这个特点使得协程更多的被拿来与常规函数进行比较,而不是与线程。当然,线程比协程复杂许多,功能也更强大,所以我建议大家牢牢地掌握线程即可:Python线程指南
这一节里我也就不列举关于协程的例子了,以下介绍的方法了解即可。
Python 2.5对生成器的增强实现了协程的其他特点,在这个版本中,生成器加入了如下方法:
1 2 3 4 5 6 7 8 9 10 | >>> def repeater(): ... n = 0 ... while True: ... n = (yield n) ... >>> r = repeater() >>> r.next() 0 >>> r.send(10) 10 |
*别为没见到协程的例子遗憾,协程最常见的用处其实就是生成器。
4.4. 一个有趣的库:pipe这一节里我要向诸位简要介绍pipe。pipe并不是Python内置的库,如果你安装了easy_install,直接可以安装它,否则你需要自己下载它:
之所以要介绍这个库,是因为它向我们展示了一种很有新意的使用迭代器和生成器的方式:流。pipe将可迭代的数据看成是流,类似于 linux,pipe使用'|'传递数据流,并且定义了一系列的“流处理”函数用于接受并处理数据流,并最终再次输出数据流或者是将数据流归纳得到一个结 果。我们来看一些例子。
第一个,非常简单的,使用add求和:
1 2 3 | >>> from pipe import * >>> range(5) | add 10 |
求偶数和需要使用到where,作用类似于内建函数filter,过滤出符合条件的元素:
1 2 | >>> range(5) | where(lambda x: x % 2 == 0) | add 6 |
还记得我们定义的斐波那契数列生成器吗?求出数列中所有小于10000的偶数和需要用到take_while,与itertools的同名函数有类似的功能,截取元素直到条件不成立:
1 2 3 4 5 | >>> fib = fibonacci >>> fib() | where(lambda x: x % 2 == 0)\ ... | take_while(lambda x: x < 10000)\ ... | add 3382 |
需要对元素应用某个函数可以使用select,作用类似于内建函数map;需要得到一个列表,可以使用as_list:
1 2 | >>> fib() | select(lambda x: x ** 2) | take_while(lambda x: x < 100) | as_list [1, 1, 4, 9, 25, 64] |
pipe中还包括了更多的流处理函数。你甚至可以自己定义流处理函数,只需要定义一个生成器函数并加上修饰器Pipe。如下定义了一个获取元素直到索引不符合条件的流处理函数:
1 2 3 4 5 6 | >>> @Pipe ... def take_while_idx(iterable, predicate): ... for idx, x in enumerate(iterable): ... if predicate(idx): yield x ... else: return ... |
使用这个流处理函数获取fib的前10个数字:
1 2 | >>> fib() | take_while_idx(lambda x: x < 10) | as_list [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] |
更多的函数就不在这里介绍了,你可以查看pipe的源文件,总共600行不到的文件其中有300行是文档,文档中包含了大量的示例。
pipe实现起来非常简单,使用Pipe装饰器,将普通的生成器函数(或者返回迭代器的函数)代理在一个实现了__ror__方法的普通类实例上即可,但是这种思路真的很有趣。
函数式编程指南全文到这里就全部结束了,希望这一系列文章能给你带来帮助。希望大家都能看到一些结构式编程之外的编程方式,并且能够熟练地在恰当的地方使用 :)
明天我会整理一个目录放上来方便查看,并且列出一些供参考的文章。遗憾的是这些文章几乎都是英文的,请努力学习英语吧 - -#