1 迭代器
迭代器只不过是一个实现迭代器协议的容器对象。它基于两个方法:
next 返回容器的下一个项目;
__iter__ 返回迭代器本身
迭代器可以通过使用iter内奸函数和一个序列来创建,示例如下:
In [1]: i=iter('abc') In [2]: i.next() Out[2]: 'a' In [3]: i.next() Out[3]: 'b' In [4]: i.next() Out[4]: 'c' In [5]: i.next() --------------------------------------------------------------------------- stopiteration Traceback (most recent call last) <ipython-input-5-e590fe0d22f8> in <module>() ----> 1 i.next() stopiteration:
当序列遍历完时,将抛出一个stopiteration异常。这将使迭代器与循环兼容,因为它们将捕获这个异常以停止循环。要创建定制的跌打器,可以编写一个具有next方法的类。只要该类能够提供返回迭代器实例的__iter__特殊方法。
In [6]: class MyIterator(object): ...: def __init__(self, step): ...: self.step = step ...: ...: def next(self): ...: """返回下一个元素""" ...: if self.step == 0: ...: raise stopiteration ...: self.step -= 1 ...: return self.step ...: ...: def __iter__(self): ...: """返回迭代器本身""" ...: return self ...: ...: ...: for el in MyIterator(4): ...: print el ...: 3 2 1 0
__iter__() 用于迭代器,Python的迭代协议要求一个 __iter__()
方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__()
方法并通过 stopiteration
异常标识迭代的完成。
迭代器本身是一个底层的特性和概念,在程序中可以没有迭代器,但是迭代器为生成器这一更有趣的特性提供了基础
2 生成器
从python2.2起,生成器提供了一种出色的方法,使得需要返回一系列元素的函数所需的代码更加简单、高效。基于yield指令,可以暂停一个函数并返回中间结果。该函数将保存执行环境并且可以在必要时恢复。
例如(这是PEP种关于迭代器的实例,)Fibonacci数列可以使用一个迭代器来实现,如下所示:
In [7]: def fibonacci(): ...: a, b = 0, 1 ...: while 1: ...: yield b ...: a, b = b, a + b ...: ...: fib = fibonacci() ...: fib.next() ...: Out[7]: 1 In [8]: fib.next() Out[8]: 1 In [9]: fib.next() Out[9]: 2 In [10]: [fib.next() for i in range(10)] Out[10]: [3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
该函数将返回一个特殊的迭代器,也就是generator对象,它知道如何保存执行环境。对它的调用是不确定的,每次豆浆产生序列中的下一个元素。这种语法很简洁,算法的不确定特性并没有影响代码的可读性。不必提供使函数可停止的方法。实际上,这看上去像是用伪代码设计的序列一样。
PEP的含义是Python增强建议(Python Enhancememnt Proposal)。它是在Python上进行修改的文件,也是开发社团讨论的一个出发点
更多关于PEP信息参考:https://www.python.org/dev/peps/pep-0001/
在开发中,生成器并不那么常用,因为开发人员还不习惯如此思考。开发人员多年来习惯于使用意图明确的函数。当需要一个将返回一个序列或在循环中执行的函数时,就应该考虑生成器。当这些元素将被传递到另一个函数中进行后续处理时,一次返回一个元素能够提高整体性能。
在这种情况下,用于处理一个元素的资源通常不如用于整个过程的资源重要,因此,它们可以仍然保持位于底层,使程序更加高效。例如,Fibonacci数列是无穷尽的,但是用来生成器它的生成器不需要在提供一个值的时候,就预先占用无穷多的内存。常见的应用常见是使用生成器的流数据缓冲区。使用这些数据的第三方程序代码可以暂停、恢复和停止生成器,所有数据在开始这一过程之前不需要导入。
例如,来自标准程序库的tokenize模块将在文本之外生成令牌,并且针对每个处理过的行返回一个迭代器,这可以被传递到一些处理中,如下所示:
>>> reader = open('test.py').next >>> tokens = tokenize.generate_tokens(reader) >>> tokens.next() (1, 'from', (1,0),4), 'from amina.quality import similarities') >>> tokens.next() (1, 'amina',5),10), 'from amina.quality import similarities')
open函数遍历了文件中的每个行,而generate_tokens则在一个管道中对其进行遍历,完成一些额外的工作。
生成器对降低程序复杂性也有帮助,并且能够提升基于多个序列的数据转换算法的性能。把每个序列当做一个迭代器,然后将它们合并到一个高级别的函数中,这是一种避免函数变得庞大、丑陋、不可理解的好办法,而且,这可以给整个处理链提供实时的反馈。
在下面的示例中,每个函数用来在序列上定义个一个转换。然后它们被链接起来应用。每次调用将处理一个元素并返回其结果,如下所示:
In [11]: def power(values): ...: for value in values: ...: print 'powering %s' % value ...: yield value ...: In [12]: def adder(values): ...: for value in values: ...: print 'adding to %s' % value ...: if value % 2 == 0: ...: yield value + 3 ...: else: ...: yield value + 2 ...: In [13]: elements = [1, 4, 7, 9, 12, 19] In [14]: res = adder(power(elements)) In [15]: res.next() powering 1 adding to 1 Out[15]: 3 In [16]: res.next() powering 4 adding to 4 Out[16]: 7 In [17]: res.next() powering 7 adding to 7 Out[17]: 9
保持代码简单,而不是数据
python引入的与生成器相关的最后一个特性是提供了与next方法调用的代码进行交互的功能。yield将变成一个表达式,而一个值可以通过名为send的新方法来传递,如下所示:
In [18]: def psychologist(): ...: print 'Please tell me your problem' ...: while True: ...: answer = (yield) ...: if answer is not None: ...: if answer.endswith('?'): ...: print "Don't ask yourself too much question" ...: elif 'good' in answer: ...: print "A that's good, go on" ...: elif 'bad' in answer: ...: print "Don't be so negatice" ...: ...: free = psychologist() ...: free.next() ...: Please tell me your problem In [19]: free.send('i feel bad') Don't be so negatice In [20]: free.send("Why I shouldn't ?") Don't ask yourself too much question In [21]: free.send("ok then i should find what is good for me") A that's good, go on
send的工作机制与next一样,但是yield将变成能够返回传入的值。因而,这个函数可以根据客户端代码来改变其行为。同时,还添加了throw和close两个函数,以完成该行为。
throw 允许客户端代码传入要抛出的任何类型的异常;
close的工作方式是相同的,但是将会刨除一个特定的异常——GeneratorExit,在这种情况下,生成器函数必须再次抛出GeneratorExit或者stopiteration异常。
In [22]: def my_generator(): ...: try: ...: yield 'something' ...: except ValueError: ...: yield 'dealing with the exception' ...: finally: ...: print "ok let's clean" ...: ...: gen = my_generator() ...: gen.next() ...: Out[22]: 'something' In [23]: gen.throw(ValueError('mean mean mean')) Out[23]: 'dealing with the exception' In [24]: gen.close() ok let's clean In [25]: gen.next() --------------------------------------------------------------------------- stopiteration Traceback (most recent call last) <ipython-input-25-b2c61ce5e131> in <module>() ----> 1 gen.next() stopiteration:
finally部分在之前的版本中是不允许使用的,它将捕获任何违背捕获的close和throw调用,是完成清理工作的推荐方式。GeneratorExit异常在生成器中是无法捕获的,因为它被编译器用来确定调用clise时是否正常退出。如果有代码与这个异常关联,那么解释程序将抛出一个系统错误并退出。
有了这3个新的方法,就有可能使用生成器来编写协同程序(coroutine)。
3 协同程序
协同程序是可以挂起、恢复,并且有多个进入点的函数。有些语言本身就提供了这种特性,如Io(http://iolanguage.com)和Lua(http://www.lua.org),它们可以实现协同的多任务和管道机制。例如,每个协同程序将消费或生成数据,然后暂停,知道其他数据被传递。
在python中,协同程序的替代者是线程,它可以实现代码块之间的交互。但是因为它们表现出一种抢先式的风格,所以必须注意资源锁,而协同程序不需要。这样的代码可能变得相当复杂,难以创建和调试。但是生成器几乎就是协同程序,添加send、throw和close,其初始的意图就是为该语言提供一种类似协同程序的特性。
PEP 342(http://www.python.org/dev/peps/pep-0342)实例化了生成器的新行为,也提供了创建协同程序的调度程序的完整实例。这个模式被称为Trampoline,可以被看做生成和消费数据的协同程序之间的媒介。它使用一个队列将协同程序连接在一起。
在Pypi中multitask模块实现了这一模式,使用也十分简单,如下所示
import multitask import time def coroutine_1(): for i in range(3): print 'c1' yield i def coroutine_2(): for i in range(3): print 'c2' yield i multitask.add(coroutine_1()) multitask.add(coroutine_2()) multitask.run() c1 c2 c1 c2 c1 c2
在协同程序之间的写作,最经典的例子是接受来自多个客户的查询,并将每个查询委托给对此做出响应的新线程的服务器应用程序。要使用协同程序来实现这一模式,首先要编写一个负责接受查询的协同程序(服务器),以及另一个处理它们的协同程序(句柄)。第一个协同程序在trampoline中为每一个请求放置一个新的句柄。
multitask包为套接字处理(如echo服务器)提供了很好的API,通过它实现程序很简单,如下所示
from __future__ import with_statement from contextlib import closing import socket import multitask def client_handler(sock): with closing(sock): while True: data = (yield multitask.recv(sock,1024)) if not data: break yield multitask.send(sock,data) def echo_server(hostname,port): addrinfo = socket.getaddrinfo(hostname,port, socket.AF_UNSPEC, socket.soCK_STREAM) (family, socktype, proto,canonname, sockaddr) = addrinfo[0] with closing(socket.socket(family,socktype,proto)) as sock: sock.setsockopt(socket.soL_SOCKET, socket.so_REUSEADDR,1) sock.bind(sockaddr) sock.listen(5) while True: multitask.add(client_handler((yield multitask.accept(sock))[0])) if __name__ == '__main__': import sys hostname = None port = 1111 if len(sys.argv) > 1: hostname = sys.argv[1] if len(sys.argv) > 2: prot = int(sys.argv[2]) multitask.add(echo_server(hostname, port)) try: multitask.run() except KeyboardInterrupt: pass
另一种协同程序实现
greenlet是另一种程序库,它的特性之一就是为Python协同程序提供了一个良好的实现。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。