【问题标题】:How to do a recursive sub-folder search and return files in a list?如何进行递归子文件夹搜索并返回列表中的文件?
【发布时间】:2013-08-25 23:49:18
【问题描述】:

我正在编写一个脚本,以递归方式遍历主文件夹中的子文件夹,并根据某种文件类型构建一个列表。我的脚本有问题。目前设置如下:

for root, subFolder, files in os.walk(PATH):
    for item in files:
        if item.endswith(".txt") :
            fileNamePath = str(os.path.join(root,subFolder,item))

问题在于subFolder 变量正在拉入子文件夹列表,而不是 ITEM 文件所在的文件夹。我之前正在考虑为子文件夹运行一个 for 循环并加入路径的第一部分,但我想我会仔细检查是否有人在此之前有任何建议。

【问题讨论】:

    标签: python list recursion os.walk


    【解决方案1】:

    您应该使用您称之为rootdirpath。提供了dirnames,因此如果存在您不希望os.walk 递归到的文件夹,您可以对其进行修剪。

    import os
    result = [os.path.join(dp, f) for dp, dn, filenames in os.walk(PATH) for f in filenames if os.path.splitext(f)[1] == '.txt']
    

    编辑:

    在最近一次投票后,我突然想到glob 是一个更好的按扩展选择的工具。

    import os
    from glob import glob
    result = [y for x in os.walk(PATH) for y in glob(os.path.join(x[0], '*.txt'))]
    

    也是一个生成器版本

    from itertools import chain
    result = (chain.from_iterable(glob(os.path.join(x[0], '*.txt')) for x in os.walk('.')))
    

    Edit2 for Python 3.4+

    from pathlib import Path
    result = list(Path(".").rglob("*.[tT][xX][tT]"))
    

    【讨论】:

    • '*.[Tt][Xx][Tt]' 全局模式将使搜索不区分大小写。
    • @SergiyKolesnikov,谢谢,我在底部的编辑中使用了它。请注意,rglob 在 Windows 平台上是不敏感的 - 但它不是可移植的。
    • @JohnLaRooy 它也适用于 glob(此处为 Python 3.6):glob.iglob(os.path.join(real_source_path, '**', '*.[xX][mM][lL]')
    • @Sergiy:您的iglob 不适用于子文件夹或以下文件夹中的文件。您需要添加recursive=True
    • @user136036,“更好”并不总是意味着最快。有时可读性和可维护性也很重要。
    【解决方案2】:

    Python 3.5 中的更改:支持使用“**”的递归 glob。

    glob.glob() 得到了一个新的recursive parameter

    如果您想获取my_path 下的每个.txt 文件(递归包括子目录):

    import glob
    
    files = glob.glob(my_path + '/**/*.txt', recursive=True)
    
    # my_path/     the dir
    # **/       every file and dir under my_path
    # *.txt     every file that ends with '.txt'
    

    如果你需要一个迭代器,你可以使用iglob 作为替代:

    for file in glob.iglob(my_path, recursive=True):
        # ...
    

    【讨论】:

    • TypeError: glob() got an unexpected keyword argument 'recursive'
    • 它应该可以工作。确保使用 >= 3.5 的版本。我在答案中添加了指向文档的链接以获取更多详细信息。
    • 这就是为什么,我在 2.7
    • 为什么列表理解,而不仅仅是files = glob.glob(PATH + '/*/**/*.txt', recursive=True)
    • 哎呀! :) 这完全是多余的。不知道是什么让我这样写。感谢您提及!我会解决的。
    【解决方案3】:

    这不是最pythonic的答案,但我会把它放在这里是为了好玩,因为它是关于递归的一堂简洁的课

    def find_files( files, dirs=[], extensions=[]):
        new_dirs = []
        for d in dirs:
            try:
                new_dirs += [ os.path.join(d, f) for f in os.listdir(d) ]
            except OSError:
                if os.path.splitext(d)[1] in extensions:
                    files.append(d)
    
        if new_dirs:
            find_files(files, new_dirs, extensions )
        else:
            return
    

    在我的机器上,我有两个文件夹,rootroot2

    mender@multivax ]ls -R root root2
    root:
    temp1 temp2
    
    root/temp1:
    temp1.1 temp1.2
    
    root/temp1/temp1.1:
    f1.mid
    
    root/temp1/temp1.2:
    f.mi  f.mid
    
    root/temp2:
    tmp.mid
    
    root2:
    dummie.txt temp3
    
    root2/temp3:
    song.mid
    

    假设我想在这两个目录中找到所有.txt 和所有.mid 文件,那么我可以这样做

    files = []
    find_files( files, dirs=['root','root2'], extensions=['.mid','.txt'] )
    print(files)
    
    #['root2/dummie.txt',
    # 'root/temp2/tmp.mid',
    # 'root2/temp3/song.mid',
    # 'root/temp1/temp1.1/f1.mid',
    # 'root/temp1/temp1.2/f.mid']
    

    【讨论】:

      【解决方案4】:

      我会将John La Rooy's list comprehension 翻译成嵌套的for,以防其他人无法理解。

      result = [y for x in os.walk(PATH) for y in glob(os.path.join(x[0], '*.txt'))]
      

      应该相当于:

      import glob
      import os
      
      result = []
      
      for x in os.walk(PATH):
          for y in glob.glob(os.path.join(x[0], '*.txt')):
              result.append(y)
      

      这是list comprehension 以及函数os.walkglob.glob 的文档。

      【讨论】:

      • 这个答案在 Python 3.7.3 中对我有用。 glob.glob(..., recursive=True)list(Path(dir).glob(...')) 没有。
      【解决方案5】:

      新的pathlib 库将其简化为一行:

      from pathlib import Path
      result = list(Path(PATH).glob('**/*.txt'))
      

      你也可以使用生成器版本:

      from pathlib import Path
      for file in Path(PATH).glob('**/*.txt'):
          pass
      

      这将返回Path 对象,您可以将其用于几乎任何事情,或者通过file.name 将文件名作为字符串获取。

      【讨论】:

        【解决方案6】:

        递归是 Python 3.5 中的新功能,因此它不适用于 Python 2.7。这是使用 r 字符串的示例,因此您只需要在 Win、Lin、...上提供路径即可

        import glob
        
        mypath=r"C:\Users\dj\Desktop\nba"
        
        files = glob.glob(mypath + r'\**\*.py', recursive=True)
        # print(files) # as list
        for f in files:
            print(f) # nice looking single line per file
        

        注意:它将列出所有文件,无论它应该有多深。

        【讨论】:

          【解决方案7】:

          此函数将递归地仅将文件放入列表中。

          import os
          
          
          def ls_files(dir):
              files = list()
              for item in os.listdir(dir):
                  abspath = os.path.join(dir, item)
                  try:
                      if os.path.isdir(abspath):
                          files = files + ls_files(abspath)
                      else:
                          files.append(abspath)
                  except FileNotFoundError as err:
                      print('invalid directory\n', 'Error: ', err)
              return files
          

          【讨论】:

            【解决方案8】:

            您可以通过这种方式返回绝对路径文件列表。

            def list_files_recursive(path):
                """
                Function that receives as a parameter a directory path
                :return list_: File List and Its Absolute Paths
                """
            
                import os
            
                files = []
            
                # r = root, d = directories, f = files
                for r, d, f in os.walk(path):
                    for file in f:
                        files.append(os.path.join(r, file))
            
                lst = [file for file in files]
                return lst
            
            
            if __name__ == '__main__':
            
                result = list_files_recursive('/tmp')
                print(result)
            
            

            【讨论】:

              【解决方案9】:

              如果您不介意安装额外的灯光库,可以这样做:

              pip install plazy
              

              用法:

              import plazy
              
              txt_filter = lambda x : True if x.endswith('.txt') else False
              files = plazy.list_files(root='data', filter_func=txt_filter, is_include_root=True)
              

              结果应该是这样的:

              ['data/a.txt', 'data/b.txt', 'data/sub_dir/c.txt']
              

              它适用于 Python 2.7 和 Python 3。

              Github:https://github.com/kyzas/plazy#list-files

              免责声明:我是plazy 的作者。

              【讨论】:

                【解决方案10】:

                这似乎是我能想到的最快的解决方案,并且os.walk 快,并且比任何glob 解决方案都快很多。 p>

                • 它还会免费为您提供所有嵌套子文件夹的列表。
                • 您可以搜索多个不同的扩展名。
                • 您还可以通过将f.path 更改为f.name 来选择返回完整路径或仅返回文件名称(不要更改子文件夹!)。

                参数:dir: str, ext: list.
                函数返回两个列表:subfolders, files

                详细的速度分析见下文。

                def run_fast_scandir(dir, ext):    # dir: str, ext: list
                    subfolders, files = [], []
                
                    for f in os.scandir(dir):
                        if f.is_dir():
                            subfolders.append(f.path)
                        if f.is_file():
                            if os.path.splitext(f.name)[1].lower() in ext:
                                files.append(f.path)
                
                
                    for dir in list(subfolders):
                        sf, f = run_fast_scandir(dir, ext)
                        subfolders.extend(sf)
                        files.extend(f)
                    return subfolders, files
                
                
                subfolders, files = run_fast_scandir(folder, [".jpg"])
                

                如果您需要文件大小,您还可以创建一个sizes 列表并像这样添加f.stat().st_size 以显示 MiB:

                sizes.append(f"{f.stat().st_size/1024/1024:.0f} MiB")
                

                速度分析

                用于获取所有子文件夹和主文件夹中具有特定文件扩展名的所有文件的各种方法。

                tl;博士:

                • fast_scandir 显然胜出,速度是所有其他解决方案的两倍,除了 os.walk。
                • os.walk 排名第二,稍慢。
                • 使用glob 会大大减慢进程。
                • 没有一个结果使用自然排序。这意味着结果将按如下方式排序:1、10、2。要获得自然排序(1、2、10),请查看https://stackoverflow.com/a/48030307/2441026

                **结果:**
                fast_scandir    took  499 ms. Found files: 16596. Found subfolders: 439
                os.walk         took  589 ms. Found files: 16596
                find_files      took  919 ms. Found files: 16596
                glob.iglob      took  998 ms. Found files: 16596
                glob.glob       took 1002 ms. Found files: 16596
                pathlib.rglob   took 1041 ms. Found files: 16596
                os.walk-glob    took 1043 ms. Found files: 16596
                

                测试是使用 W7x64、Python 3.8.1、20 次运行完成的。 439 个(部分嵌套)子文件夹中有 16596 个文件。
                find_files 来自 https://stackoverflow.com/a/45646357/2441026,可让您搜索多个扩展名。
                fast_scandir 是我自己编写的,还会返回子文件夹列表。你可以给它一个扩展列表来搜索(我测试了一个列表,其中包含一个简单的if ... == ".jpg" 的条目,并且没有显着差异)。


                # -*- coding: utf-8 -*-
                # Python 3
                
                
                import time
                import os
                from glob import glob, iglob
                from pathlib import Path
                
                
                directory = r"<folder>"
                RUNS = 20
                
                
                def run_os_walk():
                    a = time.time_ns()
                    for i in range(RUNS):
                        fu = [os.path.join(dp, f) for dp, dn, filenames in os.walk(directory) for f in filenames if
                                  os.path.splitext(f)[1].lower() == '.jpg']
                    print(f"os.walk\t\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")
                
                
                def run_os_walk_glob():
                    a = time.time_ns()
                    for i in range(RUNS):
                        fu = [y for x in os.walk(directory) for y in glob(os.path.join(x[0], '*.jpg'))]
                    print(f"os.walk-glob\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")
                
                
                def run_glob():
                    a = time.time_ns()
                    for i in range(RUNS):
                        fu = glob(os.path.join(directory, '**', '*.jpg'), recursive=True)
                    print(f"glob.glob\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")
                
                
                def run_iglob():
                    a = time.time_ns()
                    for i in range(RUNS):
                        fu = list(iglob(os.path.join(directory, '**', '*.jpg'), recursive=True))
                    print(f"glob.iglob\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")
                
                
                def run_pathlib_rglob():
                    a = time.time_ns()
                    for i in range(RUNS):
                        fu = list(Path(directory).rglob("*.jpg"))
                    print(f"pathlib.rglob\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")
                
                
                def find_files(files, dirs=[], extensions=[]):
                    # https://stackoverflow.com/a/45646357/2441026
                
                    new_dirs = []
                    for d in dirs:
                        try:
                            new_dirs += [ os.path.join(d, f) for f in os.listdir(d) ]
                        except OSError:
                            if os.path.splitext(d)[1].lower() in extensions:
                                files.append(d)
                
                    if new_dirs:
                        find_files(files, new_dirs, extensions )
                    else:
                        return
                
                
                def run_fast_scandir(dir, ext):    # dir: str, ext: list
                    # https://stackoverflow.com/a/59803793/2441026
                
                    subfolders, files = [], []
                
                    for f in os.scandir(dir):
                        if f.is_dir():
                            subfolders.append(f.path)
                        if f.is_file():
                            if os.path.splitext(f.name)[1].lower() in ext:
                                files.append(f.path)
                
                
                    for dir in list(subfolders):
                        sf, f = run_fast_scandir(dir, ext)
                        subfolders.extend(sf)
                        files.extend(f)
                    return subfolders, files
                
                
                
                if __name__ == '__main__':
                    run_os_walk()
                    run_os_walk_glob()
                    run_glob()
                    run_iglob()
                    run_pathlib_rglob()
                
                
                    a = time.time_ns()
                    for i in range(RUNS):
                        files = []
                        find_files(files, dirs=[directory], extensions=[".jpg"])
                    print(f"find_files\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(files)}")
                
                
                    a = time.time_ns()
                    for i in range(RUNS):
                        subf, files = run_fast_scandir(directory, [".jpg"])
                    print(f"fast_scandir\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(files)}. Found subfolders: {len(subf)}")
                

                【讨论】:

                • 很好的解决方案,但我遇到了一个问题,我花了一点时间才弄清楚。使用您的fast_scandir 代码,当它遇到以'.' 开头且没有扩展名(例如.DS_Store.gitignore)的文件路径时,if os.path.splitext(f.name)[1].lower() in ext 将始终返回true,这实际上是在询问if '' in '.jpg'在你的例子中。我建议添加长度检查(即if len(os.path.splitext(f.name)[1]) &gt; 0 and os.path.splitext(f.name)[1].lower() in ext)。
                • @BrandonHunter,它不返回 True。 print( os.path.splitext(".DS_Store")[1].lower() in [".jpg"] ) -> False。请记住 ext 是一个列表,而不是一个字符串。
                • 您可以消除此函数的递归性质,方法是在函数开头将dir 附加到subfolders,然后添加一个遍历subfolders 的外循环。这应该会带来非常小的速度提升,尤其是对于非常深的目录结构。如果您需要返回 subfoldersfiles 以外的内容,它还可以释放函数的输出。请注意,根据您添加和访问subfolders 元素的方式,输出的顺序可能会有所不同。
                • 看起来这不是真的。在对更大数据集的代码 sn-p 进行基准测试时,它比使用 glob 的代码花费更多时间。但是代码按预期工作。
                【解决方案11】:

                您的原始解决方案几乎是正确的,但变量“root”在递归路径时会动态更新。 os.walk() 是一个递归生成器。每个(根、子文件夹、文件)元组集都是针对特定根的设置方式。

                root = 'C:\\'
                subFolder = ['Users', 'ProgramFiles', 'ProgramFiles (x86)', 'Windows', ...]
                files = ['foo1.txt', 'foo2.txt', 'foo3.txt', ...]
                
                root = 'C:\\Users\\'
                subFolder = ['UserAccount1', 'UserAccount2', ...]
                files = ['bar1.txt', 'bar2.txt', 'bar3.txt', ...]
                
                ...
                

                我对您的代码稍作调整以打印完整列表。

                import os
                for root, subFolder, files in os.walk(PATH):
                    for item in files:
                        if item.endswith(".txt") :
                            fileNamePath = str(os.path.join(root,item))
                            print(fileNamePath)
                

                希望这会有所帮助!

                编辑:(基于反馈)

                OP 误解/错误标记了 subFolder 变量,因为它实际上是 “根”中的所有子文件夹。因此,OP,您正在尝试执行 os.path.join(str, list, str),这可能不会像您预期的那样工作。

                为了帮助增加清晰度,您可以尝试以下标签方案:

                import os
                for current_dir_path, current_subdirs, current_files in os.walk(RECURSIVE_ROOT):
                    for aFile in current_files:
                        if aFile.endswith(".txt") :
                            txt_file_path = str(os.path.join(current_dir_path, aFile))
                            print(txt_file_path)
                

                【讨论】:

                • 优雅的解决方案 - 感谢您解释 walk 的递归生成器!
                • 从某种意义上说,这应该是公认的答案,尽管我觉得它也许可以更详细地解释 OP 的错误。
                • @triplee:添加了详细信息。感谢您的反馈。 :)
                【解决方案12】:

                您可以使用 glob 模块中的“递归”设置来搜索子目录

                例如:

                import glob
                glob.glob('//Mypath/folder/**/*',recursive = True)
                

                第二行将返回该文件夹位置的子目录中的所有文件(注意,您需要在文件夹字符串末尾添加“**/*”字符串才能执行此操作。)

                如果你特别想在你的子目录深处找到文本文件,你可以使用

                glob.glob('//Mypath/folder/**/*.txt',recursive = True)
                

                【讨论】:

                  【解决方案13】:

                  最简单最基本的方法:

                  import os
                  for parent_path, _, filenames in os.walk('.'):
                      for f in filenames:
                          print(os.path.join(parent_path, f))
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2019-05-11
                    • 2018-11-25
                    • 2017-02-12
                    • 2014-12-08
                    • 2012-10-28
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多