【问题标题】:How can I traverse a file system with a generator?如何使用生成器遍历文件系统?
【发布时间】:2010-12-14 11:47:27
【问题描述】:

我正在尝试创建一个实用程序类来遍历目录中的所有文件,包括子目录和子子目录中的文件。我尝试使用发电机,因为发电机很酷;但是,我遇到了障碍。


def grab_files(directory):
    for name in os.listdir(directory):
        full_path = os.path.join(directory, name)
        if os.path.isdir(full_path):
            yield grab_files(full_path)
        elif os.path.isfile(full_path):
            yield full_path
        else:
            print('Unidentified name %s. It could be a symbolic link' % full_path)

当生成器到达一个目录时,它只是简单地生成新生成器的内存位置;它没有给我目录的内容。

如何让生成器产生目录的内容而不是新的生成器?

如果已经有一个简单的库函数可以递归地列出目录结构中的所有文件,请告诉我。我不打算复制库函数。

【问题讨论】:

    标签: python recursion iterator generator yield


    【解决方案1】:

    既然可以使用os.walk,为什么还要重新发明轮子

    import os
    for root, dirs, files in os.walk(path):
        for name in files:
            print os.path.join(root, name)
    

    os.walk 是一个生成器,它通过自上而下或自下而上遍历目录树来生成目录树中的文件名

    【讨论】:

    • 但话又说回来,通过重新发明轮子,我们可以os.cycle 而不是os.walk...
    • 我认为这是个玩笑……“重新发明轮子”?步行与骑自行车?相当不错.. :)
    • 是的,内德,开个玩笑。对 os.walk() 的建议是可行的方法,除非你只是想了解生成器并将目录遍历用作它的实际练习。
    • @Ned:我真的只是捂脸。
    • os.walk 可能是一个生成器,但它的粒度是目录级别,它返回的文件是一个列表。如果你有一个包含数百万个文件的目录,那么使用 os.walk 祝你好运。至少在 2.7 中是这样的。
    【解决方案2】:

    我同意 os.walk 解决方案

    出于纯粹的迂腐目的,尝试迭代生成器对象,而不是直接返回它:

    
    def grab_files(directory):
        for name in os.listdir(directory):
            full_path = os.path.join(directory, name)
            if os.path.isdir(full_path):
                for entry in grab_files(full_path):
                    yield entry
            elif os.path.isfile(full_path):
                yield full_path
            else:
                print('Unidentified name %s. It could be a symbolic link' % full_path)
    

    【讨论】:

    • 感谢您的示例。在我发布问题大约五分钟后,我想出了这个解决方案。 XD
    【解决方案3】:

    从 Python 3.4 开始,您可以使用内置 pathlib 模块中的 glob() 方法:

    import pathlib
    p = pathlib.Path('.')
    list(p.glob('**/*'))    # lists all files recursively
    

    【讨论】:

    • 确认一下,type(p.glob('**/*')) 确实返回了generator
    【解决方案4】:

    从 Python 3.4 开始,您可以使用 Pathlib 模块:

    In [48]: def alliter(p):
       ....:     yield p
       ....:     for sub in p.iterdir():
       ....:         if sub.is_dir():
       ....:             yield from alliter(sub)
       ....:         else:
       ....:             yield sub
       ....:             
    
    In [49]: g = alliter(pathlib.Path("."))                                                                                                                                                              
    
    In [50]: [next(g) for _ in range(10)]
    Out[50]: 
    [PosixPath('.'),
     PosixPath('.pypirc'),
     PosixPath('.python_history'),
     PosixPath('lshw'),
     PosixPath('.gstreamer-0.10'),
     PosixPath('.gstreamer-0.10/registry.x86_64.bin'),
     PosixPath('.gconf'),
     PosixPath('.gconf/apps'),
     PosixPath('.gconf/apps/gnome-terminal'),
     PosixPath('.gconf/apps/gnome-terminal/%gconf.xml')]
    

    这是sjthebats answer 的面向对象版本必不可少的。 注意Path.glob** 模式只返回目录!

    【讨论】:

    • 对于处理目录中许多文件的人来说,我相信这是这个答案上唯一真正的迭代解决方案,并且可能是 python(3) 标准库中唯一的高级方法。它可能应该作为选项添加到iterdir()
    • @KobeJohn yield from alliter(sub) 不是在生成器 alliter 中,而是递归而不是迭代?
    • 你是对的。我的意思是,它无需先对目录中的所有文件进行完整统计即可为您提供结果。因此,即使您有大量文件,它也可以立即生成结果。
    【解决方案5】:

    os.scandir() 是一个“函数返回目录条目以及文件属性信息,在许多常见用例中提供更好的性能[比os.listdir()]。”它是一个不使用 os.listdir() 内部的迭代器。

    【讨论】:

      【解决方案6】:

      您可以使用path.py。不幸的是,作者的网站不再存在,但您仍然可以从 PyPI 下载代码。该库是 os 模块中路径函数的包装器。

      path.py 提供了一个 walkfiles() 方法,该方法返回一个生成器,该生成器对目录中的所有文件进行递归迭代:

      >>> from path import path
      >>> print path.walkfiles.__doc__
       D.walkfiles() -> iterator over files in D, recursively.
      
              The optional argument, pattern, limits the results to files
              with names that match the pattern.  For example,
              mydir.walkfiles('*.tmp') yields only files with the .tmp
              extension.
      
      >>> p = path('/tmp')
      >>> p.walkfiles()
      <generator object walkfiles at 0x8ca75a4>
      >>> 
      

      【讨论】:

        【解决方案7】:

        gerrit 答案的附录。我想让一些东西更灵活。

        列出pth中与给定pattern匹配的所有文件,如果only_fileFalse,也可以列出目录

        from pathlib import Path
        
        def walk(pth=Path('.'), pattern='*', only_file=True) :
            """ list all files in pth matching a given pattern, can also list dirs if only_file is False """
            if pth.match(pattern) and not (only_file and pth.is_dir()) :
                yield pth
            for sub in pth.iterdir():
                if sub.is_dir():
                    yield from walk(sub, pattern, only_file)
                else:
                    if sub.match(pattern) :
                        yield sub
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-28
          • 2018-07-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多