递归函数中的yield

53 投票
9 回答
14075 浏览
提问于 2025-04-16 21:53

我想对某个路径下的所有文件做一些操作。我不想事先收集所有文件的名字再去处理它们,所以我试了这个:

import os
import stat

def explore(p):
  s = ''
  list = os.listdir(p)
  for a in list:
    path = p + '/' + a
    stat_info = os.lstat(path )
    if stat.S_ISDIR(stat_info.st_mode):
     explore(path)
    else:
      yield path

if __name__ == "__main__":
  for x in explore('.'):
    print '-->', x

但是这个代码在遇到文件夹时会跳过它们,而不是处理它们里面的内容。我哪里做错了呢?

9 个回答

37

问题出在这行代码上:

explore(path)

这行代码到底是干嘛的呢?

  • 它调用了 explore 函数,并传入了一个新的 path 参数
  • explore 函数运行后,会创建一个生成器
  • 这个生成器会被返回到调用 explore(path) 的地方 ……
  • 然后就被丢弃了

为什么会被丢弃呢?因为它没有被赋值给任何变量,也没有被遍历过——就完全被忽视了。

如果你想对结果做点什么,那你得先处理这些结果!;)

修复你代码的最简单方法是:

for name in explore(path):
    yield name

当你确信自己明白发生了什么之后,可能会想用 os.walk() 来替代。

一旦你升级到 Python 3.3(假设一切顺利),你就可以使用新的 yield from 语法,那时修复你代码的最简单方法将是:

yield from explore(path)
129

迭代器并不是像那样递归工作的。你需要重新返回每一个结果,也就是说要把

explore(path)

换成类似这样的东西

for value in explore(path):
    yield value

Python 3.3 增加了一种语法 yield from X,这是根据PEP 380 提出的,目的是为了简化这个过程。这样你就可以这样做:

yield from explore(path)

如果你在使用生成器作为协程,这个语法还支持使用generator.send()将值传回递归调用的生成器。上面简单的 for 循环是无法做到的。

26

使用 os.walk,别自己重新发明轮子。

特别是,参考库文档中的示例,这里有一个未经测试的尝试:

import os
from os.path import join

def hellothere(somepath):
    for root, dirs, files in os.walk(somepath):
        for curfile in files:
            yield join(root, curfile)


# call and get full list of results:
allfiles = [ x for x in hellothere("...") ]

# iterate over results lazily:
for x in hellothere("..."):
    print x

撰写回答