Python之yield、yield from(三)

前面两篇基本上把yield、yield from语法语义讲清楚了,然而反过来,我们思考一下,这个yield、yield from这个到底有什么好处,费了很多劲学习这个,举了几个玩具般的例子,然而实战中到底有什么优势?这篇文章将试图解答这个问题。

程序需求如图:

Python之yield、yield from(三)

我们要从classes(班级)目录下,查找某个学生的成绩。class_1.txt代表一班的成绩,class_2.txt代表二班的成绩。

代码如下:

import os
import re

#查找目录下的文件
def gen_find(filedir):
    for path, _, filelist in os.walk(filedir):
        for name in filelist:
            yield os.path.join(path,name)

#传入所有需要打开的文件名
def gen_opener(filenames):
    for filename in filenames:
        with open(filename,"rt") as f:
            yield f

#连接每个文件的查询结果
def gen_concate_file(iterators,name):
    for it in iterators:
        yield from gen_grep(name,it)

#查找文件中需要匹配的内容
def gen_grep(name,lines):
    pattern = re.compile(name)
    for line in lines:
        if pattern.search(line):
            yield line

def main():
    filenames = gen_find('classes')
    files = gen_opener(filenames)
    results = gen_concate_file(files,'王')
    print(list(results))
        
if __name__ == '__main__':
    main()

上面的程序实现:查找classes目录下,所有班级中名字含有“王”的学生成绩。该程序有以下特色:

1,每个生成器都很小,相对独立,编写维护会比较方便。

2,每个yield作为一个单独的数据单元,传递给处理管道的下一阶段。就像一个链表一样,将这些执行单元连接在一起,一个一个的执行。

3,内存效率高。这可能是yield、yield from最大优势所在。gen_opener()会打开一个文件,下一次迭代时,上一文件已经关闭。内存中一次只加载一个文件,不需要将所有文件加载到内存中去。其它的方法也是类似,一个接一个的执行,回收,再继续执行下一个。

4,gen_concate_file()方法会将每个文件的查询结果包装成一个生成器(即1个文件1个生成器),然后将这些生成器连接起来,合并成一个生成器。如果我们采用itertools.chain(),它需要将所有可迭代对象作为参数传入(如:itertools.chain(*files))。而gen_opener()一次只打开一个文件,所以不能用chain()。

 

总结,yield、yield from规则相当复杂,没有一个明确的定义,作用范围也不是很明显,掌握起来非常有难度。