递归函数中的收益

我正在尝试对给定路径下的所有文件进行操作。 我不想事先收集所有文件名,然后对它们进行处理,因此我尝试了以下操作:

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个解决方案
126 votes

迭代器不会像这样递归地工作。 您必须通过替换来重新产生每个结果

explore(path)

用类似的东西

for value in explore(path):
    yield value

Python 3.3添加了PEP 380中提出的语法for,以达到此目的。 有了它,您可以改为执行以下操作:

yield from explore(path)

如果您将生成器用作协程,则此语法还支持使用for将值传递回递归调用的生成器。 上面的简单for循环不会。

ON STRIKE - Jeremy Banks answered 2020-01-07T18:58:37Z
36 votes

问题是这行代码:

explore(path)

它有什么作用?

  • 用新的path调用yield from
  • yield from运行,创建一个发电机
  • 发电机返回yield from被执行的地点。 。 。
  • 并被丢弃

为什么将其丢弃? 它没有分配给任何东西,也没有被迭代-完全被忽略了。

如果您想对结果做些事,那么您就必须对它们做些事! ;)

修改代码的最简单方法是:

for name in explore(path):
    yield name

当您确定自己了解发生了什么时,您可能需要改用yield from

迁移到Python 3.3(假设所有工作按计划进行)后,您将能够使用新的yield from语法,到那时,修复代码的最简单方法是:

yield from explore(path)
Ethan Furman answered 2020-01-07T18:59:42Z
25 votes

使用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
phooji answered 2020-01-07T18:58:04Z
8 votes

更改此:

explore(path)

对此:

for subpath in explore(path):
    yield subpath

或按照phooji建议使用os.walk(这是更好的选择)。

Dietrich Epp answered 2020-01-07T19:00:10Z
3 votes

就像函数一样调用stat。 您应该做的是像生成器一样对其进行迭代:

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

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

MRAB answered 2020-01-07T19:00:35Z
2 votes

尝试这个:

if stat.S_ISDIR(stat_info.st_mode):
    for p in explore(path):
        yield p
satoru answered 2020-01-07T19:00:54Z
0 votes

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

但是,对于这种特定情况,os.walk可能是一种更好的方法。

Robson França answered 2020-01-07T19:01:19Z
0 votes

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

但是,除了可以做到这一点之外,这样做实际上没有任何优势。 如果首先使用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('.'))
user1149913 answered 2020-01-07T19:01:44Z
0 votes

要回答所提出的原始问题,关键是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
Hlórriði answered 2020-01-07T19:02:04Z
translate from https://stackoverflow.com:/questions/6755869/yield-in-a-recursive-function