python 迭代器、生成器和协同程序

python 迭代器、生成器和协同程序,第1张

概述python 迭代器、生成器协同程序

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   ...:3210

__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]: 1In [8]: fib.next()Out[8]: 1In [9]: fib.next()Out[9]: 2In [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 1adding to 1Out[15]: 3In [16]: res.next()powering 4adding to 4Out[16]: 7In [17]: res.next()powering 7adding to 7Out[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 problemIn [19]: free.send('i feel bad')Don't be so negaticeIn [20]: free.send("Why I shouldn't ?")Don't ask yourself too much questionIn [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 cleanIn [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协同程序提供了一个良好的实现。

总结

以上是内存溢出为你收集整理的python 迭代器、生成器和协同程序全部内容,希望文章能够帮你解决python 迭代器、生成器和协同程序所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1198236.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-03
下一篇 2022-06-03

发表评论

登录后才能评论

评论列表(0条)

保存