乍一看,如何使用生成器和协程解决实际问题似乎并不明显。但在解决系统、网络和分布式计算方面的某些编程问题时,生成器和协程特别有用。例如,如果要建立一个处理管道(本质上类似于在UNIX shell中使用一个管道),就需要用到生成器函数。第1章中给出一个这样的例子。下面给出另一个例子,其中包括关于查找、打开、读取和处理文件的一组生成器函数:
import os
import fnmatch
def find_files(topdir, pattern):
for path, dirname, filelist in os.walk(topdir):
for name in filelist:
if fnmatch.fnmatch(name, pattern):
yield os.path.join(path, name)
def opener(filenames):
for name in filenames:
f = open(name)
yield f
def cat(filelist):
for f in filelist:
for line in f:
yield line
def grep(pattern, lines):
for line in lines:
if pattern in line:
yield line
下面的例子使用这些函数建立了一个处理管道:
logs = find_files("www","access-log*")
files = opener(logs)
lines = cat(files)
pylines = grep("ultop", lines)
for line in pylines:
print line
在这个例子中,程序要处理的是顶级目录"www"的所有子目录中的所用"access-log"文件中的所有行。程序将打开每个“access-log”文件,将各行连接在一起,并通过查找子字符串“python”的过滤器进行处理。整个程序是由位于最后的for语句驱动的。该循环的每次迭代都会通过管道获得一个新值并使用之。此外,这种实现占用内存极少,因为它无需创建任何临时列表或其他大型的数据结构。
协程可用于编写数据流处理程序。以这种方式组织的程序像是反转的管道。并非通过一系列使用for循环的生成器函数获取值,而是将值发送到一些相互连接的协程中。下面给出一个例子,其中的协程函数模拟了前面给出的生成器函数:
import os
import fnmatch
@coroutine
def find_files(target):
topdir, pattern = (yield)
for path, dirname, filelist in os.walk(topdir):
for name in filelist:
if fnmatch.fnmatch(name, pattern):
target.send(os.path.join(path, name))
@coroutine
def opener(target):
while True:
name = (yield)
f = open(name)
target.send(f)
@coroutine
def cat(target):
while True:
f = (yield)
for line in f:
target.send(line)
@coroutine
def grep(pattern, target):
while True:
line = (yield)
if pattern in line:
target.send(line)
@coroutine
def printer():
while True:
line = (yield)
print line
以下代码说明了如何将这些协程连接起来,创建一个数据流处理管道:
finder = find_files(opener(cat(grep("python", printer()))))
#现在发送一个值
finder.send(("www","access-log*"))
find.send(("otherwww","access-log"))
在这个例子中,每个协程都发送数据给在它们的target参数中指定的另一个协程。和生成器的例子不同,执行完全由将数据发送到第一个协程find_files()中来驱动。接下来,这个协程将数据转入下一阶段。这个例子有一个关键地方,即协程管道永远保持活动状态,直到它显式调用close()方法为止。为此,只要需要,程序可以不断给协程中注入数据,例如本例中对于send()方法的两次重复调用。
协程可用于实现某种形式的并发。例如,可以安排一个集中式的任务管理器或事件循环,将数据发送到成百上千个用于执行各种处理任务的协程中。输入数据“被发送”到协程中,这个事实还说明,协程经常可以很容易地与使用消息队列和消息传递在程序组件之间进行通信的程序混合在一起使用。
摘自:python参考手册第4版 6.8节 87页