从生成器到协程

可迭代对象和迭代器

可迭代对象(Iterable)指的是能够使用for循环进行遍历的对象,如字符串、数组、迭代器、生成器等。

可迭代对象实现了__iter__()方法。

迭代器(Iterator)是一种特殊的可迭代对象,实现了__next__()方法。

判断一个对象obj是否是可迭代对象/迭代器:

1
2
3
4
from collections import Iterable, Iterator

isinstance(obj, Iterable)
isinstance(obj, Iterator)

iter()函数称之为迭代器工厂函数,它能将一个可迭代对象转化为迭代器,通过给非迭代器的可迭代对象添加__next__()方法,实现工厂式的加工和包装功能。

1
2
3
4
5
6
x = [1, 2, 3, 4, 5]
print(isinstance(x, Iterable)) # True
print(isinstance(x, Iterator)) # False
x_iter = iter(x)
print(isinstance(x_iter, Iterable)) # True
print(isinstance(x_iter, Iterator)) # True

生成器

生成器(Generator)本质上还是迭代器,因此迭代器的所有特性都适合于生成器。他们的区别在于实现方式不同:实现一个迭代器往往需要定义一个并且实现其__iter__()方法和__next__()方法;实现一个生成器只需要在函数中使用yield关键字。后者的实现方式更加简洁,性能与迭代器一样高效。

python中的生成器有两种基本实现方式,一种是通过yield关键字实现,一种叫做生成器表达式生成器表达式列表推导式十分相似,只需把[...]换成(...)即可。在大数据的迭代过程中,列表推导式将会耗费大量的时间和空间,此时选择生成器表达式性能将会得到显著的提升。只是简简单单地改变一个括号,程序运行的速度就能肉眼可见的变快!

1
2
3
4
5
6
from collections import Generator

l = [x**3 for x in range(100)] # 列表推导式
isinstance(l, list) # True
g = (x**3 for x in range(100)) # 生成器表达式
isinstance(g, Generator) # True

另外一种实现生成器的方法是在函数中使用yield关键字。当函数执行到yield时会返回yield指定的值,同时将函数挂起,这点同return不一样:return返回指定的值时意味着函数调用结束。当再次调用此函数时,不会从头执行,而是会接着上次挂起时的状态接着执行。

yield关键字不仅能返回数据,也能从外界接收数据,接收数据通过.send(...)方法实现。next()函数和.send()方法都能够激活生成器函数继续运行直到下一次遇到yield挂起,不同的是,.send()方法还能够向生成器函数传递值。看下面这个生硬的例子:

使用next(g)使生成器运行至yield处,函数等待外界传值给局部变量x并挂起,此时再使用g.send(3)将3传递给x,函数被激活继续向下执行,计算y等于8,此时遇到第二个yield,函数返回y的值并挂起,所以g.send(3)的返回结果为8。

如果再次执行g.send(4)会发现返回结果为空!这是因为:.send()方法激活可生成器函数并从上次挂起的地方继续执行(yield y),执行至x = yield等待外界传值,也就是说本次.send发送的消息,生成器函数内部没有变量接收,相当于一次无效的消息发送,故而没有返回值。如果再次执行g.send(4),返回结果显示为16,这与上述g.send(3)的情景相似。

通过yield关键字和.send方法,用户可以随时中断一个函数执行转而执行另一个函数,相当于手动从一个子程序的执行切换到了另一个子程序的执行。在这种子程序的切换过程中没有涉及到线程的切换,我们将一个子程序和它被执行以及被挂起时的状态称之为一个协程(Coroutine)。

>>>>>>>>>>>>> 转载请注明出处 <<<<<<<<<<<<<
0%