【问题标题】:Yield in a recursive function递归函数的产量
【发布时间】:2011-10-09 00:15:40
【问题描述】:

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

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

但是这段代码在碰到目录时会跳过目录,而不是产生它们的内容。我做错了什么?

【问题讨论】:

  • 有些语言可以产生整个序列,而不仅仅是单个元素。我不认为 Python 是其中之一。 mindscapehq.com/blog/index.php/2011/02/28/…
  • 由于标题暗示了一个比 os.walk 可以解决的更普遍的问题,考虑一下: def explore(p): if isinstance(p, (list, tuple)): for x in p : explore(p) else: yield p 这也有同样的问题。为什么它不起作用?

标签: python recursion iterator directory-structure yield


【解决方案1】:

要回答所提出的原始问题,关键是需要将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

【讨论】:

    【解决方案2】:

    问题出在这行代码:

    explore(path)
    

    它有什么作用?

    • 使用新的path 调用explore
    • explore 运行,创建生成器
    • 生成器返回到执行explore(path) 的位置。 . .
    • 并被丢弃

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

    如果你想对结果做点什么,那么,你必须对它们做点什么! ;)

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

    for name in explore(path):
        yield name
    

    当您确信自己了解正在发生的事情时,您可能希望改用 os.walk()

    一旦您迁移到 Python 3.3(假设一切都按计划进行),您将能够使用新的 yield from 语法,此时修复代码的最简单方法是:

    yield from explore(path)
    

    【讨论】:

      【解决方案3】:

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

      但这样做并没有任何优势,除了它是可能的。如果您首先使用 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('.'))
      

      【讨论】:

        【解决方案4】:

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

        explore(path)
        

        类似的东西

        for value in explore(path):
            yield value
        

        Python 3.3 添加了yield from X 的语法,正如PEP 380 中所建议的那样,用于此目的。有了它,您可以这样做:

        yield from explore(path)
        

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

        【讨论】:

        • 恕我直言,这应该是公认的答案,因为问题是关于产量和递归,而不是关于实现 os.walk 的最佳方式 ;-) !!!我在这个非常简单的循环上打破了我的头......实际上所有其他答案都在同一行......
        • 谢谢你!提到 3.3 和特定的迭代器真的很有用。
        • 正如 PyCon2014 的演讲中提到的,生成器可用于绕过递归限制! youtube.com/watch?v=D1twn9kLmYg
        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          使用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
          

          【讨论】:

          • 提供工作代码很好,但解释 OP 做错了什么,尤其是当他们要求时,更好。
          • 问题是关于产量和递归,而不是实现 os.walk 的最佳方式
          • 另外:在 Python 2 中,walk 比 listdir 慢,参见 python.org/dev/peps/pep-0471
          【解决方案7】:

          改变这个:

          explore(path)
          

          到这里:

          for subpath in explore(path):
              yield subpath
          

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

          【讨论】:

            【解决方案8】:

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

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

            编辑:您可以使用os.path.isdir(path),而不是stat 模块。

            【讨论】:

              【解决方案9】:

              试试这个:

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

              【讨论】:

                猜你喜欢
                • 2020-11-21
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-03-14
                • 2021-12-11
                • 1970-01-01
                • 2015-03-01
                相关资源
                最近更新 更多