微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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
   ...:
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 举报,一经查实,本站将立刻删除。

相关推荐