在递归函数中产生

时间:2011-07-20 00:59:02

标签: python recursion iterator directory-structure yield

我正在尝试对给定路径下的所有文件执行某些操作。我不想事先收集所有的文件名,然后用它们做一些事情,所以我尝试了这个:

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 个答案:

答案 0 :(得分:124)

迭代器不会像那样递归地工作。您必须通过替换

来重新产生每个结果
explore(path)

类似

for value in explore(path):
    yield value

Python 3.3添加了PEP 380中提出的语法yield from X,以实现此目的。有了它,你可以这样做:

yield from explore(path)

如果您正在使用generators as coroutines,则此语法还支持使用generator.send()将值传递回递归调用的生成器。上面的简单for循环不会。

答案 1 :(得分:35)

问题在于这行代码:

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)

答案 2 :(得分: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

答案 3 :(得分:8)

改变这个:

explore(path)

对此:

for subpath in explore(path):
    yield subpath

或者使用os.walk,如phooji建议的那样(这是更好的选择)。

答案 4 :(得分:3)

像函数一样调用explore。你应该做的是像生成器一样迭代它:

if stat.S_ISDIR(stat_info.st_mode):
  for p in explore(path):
    yield p
else:
  yield path

编辑:您可以使用stat代替os.path.isdir(path)模块。

答案 5 :(得分:2)

试试这个:

if stat.S_ISDIR(stat_info.st_mode):
    for p in explore(path):
        yield p

答案 6 :(得分:0)

如果您需要遍历所有文件夹和子文件夹,那么os.walk非常棒。如果你不需要它,那就像用大象枪杀死一只苍蝇。

但是,对于这个具体案例,os.walk可能是一种更好的方法。

答案 7 :(得分:0)

您还可以使用堆栈实现递归。

除了可能的事实之外,实际上没有任何优势。如果你首先使用python,性能提升可能是不值得的。

import os
import stat

def explore(p):
    '''
    perform a depth first search and yield the path elements in dfs order
        -implement the recursion using a stack because a python can't yield within a nested function call
    '''
    list_t=type(list())
    st=[[p,0]]
    while len(st)>0:
        x=st[-1][0]
        print x
        i=st[-1][1]

        if type(x)==list_t:
            if i>=len(x):
                st.pop(-1)
            else:
                st[-1][1]+=1
                st.append([x[i],0])
        else:
            st.pop(-1)
            stat_info = os.lstat(x)
            if stat.S_ISDIR(stat_info.st_mode):
                st.append([['%s/%s'%(x,a) for a in os.listdir(x)],0])
            else:
                yield x

print list(explore('.'))

答案 8 :(得分:0)

要回答最初提出的问题,关键是yield语句需要从递归中传播回来(就像return一样)。这是os.walk()的有效重新实现。我在伪VFS实现中使用它,在此我另外替换了os.listdir()和类似的调用。

import os, os.path
def walk (top, topdown=False):
    items = ([], [])
    for name in os.listdir(top):
        isdir = os.path.isdir(os.path.join(top, name))
        items[isdir].append(name)
    result = (top, items[True], items[False])
    if topdown:
        yield result
    for folder in items[True]:
        for item in walk(os.path.join(top, folder), topdown=topdown):
            yield item
    if not topdown:
        yield result