1.迭代器
迭代器只不过是一个实现迭代器协议的容器对象,它基于两个方法:
- next返回容器的下一个项目
- __iter__返回迭代器本身
迭代器可以通过使用一个iter内建函数和一个序列来创建:
In [1]: i = iter('abcd')
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()
Out[5]: 'd'
In [6]: i.next()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
/home/ubuntu/<ipython-input-6-e590fe0d22f8> in <module>()
----> 1 i.next()
StopIteration:
当序列遍历完时,会抛出一个StopIteration异常。这将使迭代器与循环兼容,因为他们将捕获这个异常以停止循环。要创建定制的迭代器,可以编写一个具有next方法的类,只要该类能够提供返回迭代器实例的__iter__特殊方法。
class MyIterator(object):
def __init__(self,step):
self.step = step
def next(self):
"""Returns the next element."""
if self.step == 0:
raise StopIteration
self.step -= 1
return self.step
def __iter__(self):
"""Returns the iterator itself."""
return self
for el in MyIterator(4):
print el
执行结果当然为3,2,1,0.迭代器本身是一个底层的特性和概念,在程序中可以没有它们,但是它们为生成器一更有趣的特定提供了基础。
2. 生成器
yield
从Python2.2起,生成器提供了一种出色的方法,使得需要返回一系列元素的函数所需的代码更加简洁高效。基于yield指令,可以暂停一个函数并返回中间结果。该函数将保存执行韩静并且可以在必要时回复。
例如:Fibonacci数列可以用一个迭代器来实现,如下:
def fibonacci():
a,b = 0, 1
while True:
yield b
a, b = b, a+b
fib = fibonacci()
print fib.next()
print fib.next()
print fib.next()
print [fib.next() for i in range(10)]
结果为:
1
1
2
[3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
该函数返回一个特殊的迭代器,也就是generator对象,它知道如何保存执行环境。对它的调用是不确定的,每次都将产生序列中的下一个元素。这种语法很简洁,算法的不确定特定并没有影响代码的可读性。不必提供使函数停止的方法。实际上,这看上去就像用伪代码设计程序序列一样。
在开发中,生成器并不那么常用,因为开发人员还不习惯这种思考。开发人员多年来习惯于使用意图明确的函数。当需要一个将返回一个序列或在循环中执行的函数时,就应该考虑生成器。当这些元素将被传递到另一个函数中以进行后续的处理时,一次返回一个元素能提高整体的性能。
在这种情况下,用于处理一个元素的资源通常不如用于整个过程的资源重。因此,它们可以仍然保持位于底层,使程序更加高效。例如,Fibonacci数列是无穷尽的,但是用来生成它的生成器不需要在提供一个值得时候,就预先占用无穷多的内存。常见的应用场景是使用生成器的数据流缓冲区。使用这些数据的第三方程序代码可以暂停、回复和停止生成器,所有数据在开始这一过程之前不需要全部导入。
生成器对降低程序复杂性也有帮助,并且能够提升基于多个序列的数据转换算法的性能。把每个序列当做一个迭代器,然后将他们合并到一个高级的函数中,这是一种避免函数变得庞大、丑陋、不可理解的好办法。而且,这可以给整个处理链提供实时的反馈。
在下面的实例中,每个函数用来在序列上定义一个转换,然后它们被链接起来应用,每次调用将处理一个元素并返回结果,如下:
def power(values):
for value in values:
print 'powering %s' % value
yield value
def adder(values):
for value in values:
print 'adding to %s' % value
if value % 2 == 0:
yield value + 3
else:
yield value + 2
elements = [1, 4, 7, 9, 12, 19]
res = adder(power(elements))
print res.next()
print res.next()
print res.next()
注意:保持代码的简单,而不是数据,拥有许多简单的处理序列的可迭代函数,要比一个复杂的、每次计算一个值得函数更好一些。
send
Python引入的与生成器相关的最后一个特定提供了与next方法调用的代码进行交互的功能。yield将变成一个表达式,而
一个值可以通过名为send的方法来传递,如下:
def psychologist():
print 'Please tell me your problems'
while True:
answer = (yield)
if answer is not None:
if answer.endswith('?'):
print ("Don't ask yourself too much questions")
elif 'good' in answer:
print "A that's good, go on"
elif 'bad' in answer:
print "Don't be so negative"
free = psychologist()
free.next()
free.send('I feel bad')
free.send("Why I shouldn't?")
free.send('OK then i shoud find what is good for me ')
执行结果为:
Please tell me your problems
Don't be so negative
Don't ask yourself too much questions
A that's good, go on
send的工作机制与next一样,但是yield将变成能够返回传入的值。因而,这个函数可以根据客户端代码来改变行为。同时,还添加了throw和close两个函数以完成该行为。它们将向生成器抛出一个错误:
- throw允许客户端代码传入一个抛出的任何类型的异常
- close的工作方式是相同的,但是将抛出一个特定的异常----GeneratorExit,在这种情况下,生成器函数必须再次抛出GeneratorExit或StopIteration异常。
def my_generator():
while True:
try:
yield 'something'
except ValueError:
yield 'dealing with the exception'
finally:
print "ok, let's clean"
In [2]: gen = my_generator()
In [3]: gen.next()
Out[3]: 'something'
In [4]: gen.next()
ok, let's clean
Out[4]: 'something'
In [5]: gen.throw(ValueError('mean mean mean'))
Out[5]: 'dealing with the exception'
In [6]: gen.close()
ok, let's clean
In [7]: gen.next()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
/tmp/<ipython-input-7-b2c61ce5e131> in <module>()
----> 1 gen.next()
StopIteration:
finally部分将捕获任何未被捕获的close和throw调用,是完成清理工作的推荐方式。GeneratorExit异常在生成器中无法捕获,因为它被编译器用来调用close时确定是否正常退出。如果有代码与这个异常关联,那么结实程序将抛出一个系统错误并退出。
有了这几个方法,就可以使用生成器来编写协同程序(coroutine)。