递归子文件夹搜索并返回列表python中的文件


117

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

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循环,然后加入路径的第一部分,但我想出了ID双重检查以了解在此之前是否有人提出建议。谢谢你的帮助!

Answers:


155

您应该使用dirpath调用的root。在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('.')))

适用于Python的Edit2 3.4+

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

1
'*。[Tt] [Xx] [Tt]'全局模式将使搜索不区分大小写。
SergiyKolesnikov

@SergiyKolesnikov,谢谢,我在底部的编辑中使用了它。请注意,rglob在Windows平台上,-不敏感-但是它并不是可移植的。
约翰·拉鲁伊

1
@JohnLaRooy它也可以使用glob(此处为Python 3.6):glob.iglob(os.path.join(real_source_path, '**', '*.[xX][mM][lL]')
SergiyKolesnikov,

@Sergiy:您iglob不能用于子子文件夹或以下子文件夹中的文件。您需要添加recursive=True
user136036

1
@ user136036,“更好”并不总是意味着最快。有时可读性和可维护性也很重要。
John La Rooy

111

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

glob.glob()有一个新的递归参数

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

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=False):
    # ...

1
TypeError:glob()得到了意外的关键字参数“递归”
Cyber​​Jacob

1
它应该正在工作。确保使用的版本> = 3.5。我在答案中添加了指向文档的链接,以获取更多详细信息。
罗塔蒂2013年

这就是为什么,我是2.7
Cyber​​Jacob

1
为什么列表理解而不仅仅是files = glob.glob(PATH + '/*/**/*.txt', recursive=True)
tobltobs 17-10-19

哎呀!:)完全多余。不知道是什么让我这样写的。感谢您提到它!我会解决的。
Rotareti

20

我将把John La Rooy的列表理解理解为nest for的表达,以防万一其他人难以理解它。

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

应等效于:

import glob

result = []

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

这是列表理解os.walkglob.glob函数的文档。


1
这个答案在Python 3.7.3中为我工作。glob.glob(..., recursive=True)list(Path(dir).glob(...'))没有。
miguelmorin

11

这似乎是我能想到的最快的解决方案,并且比任何解决方案都快os.walk而且要快得多glob

  • 它也将免费为您提供所有嵌套子文件夹的列表。
  • 您可以搜索几个不同的扩展名。
  • 您也可以选择通过改变返回的文件不管是完整路径或只是名称f.pathf.name(不改变它的子文件夹!)。

Args :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"])


速度分析

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

tl; dr:
- 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)}")

10

新的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的答案,但我会把它放在这里很有趣,因为这是递归中的一个很好的课程

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

在我的机器上,我有两个文件夹,root并且root2

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

递归是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

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


3

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

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)

2

如果您不介意安装其他灯库,则可以执行以下操作:

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


1

此功能将递归地仅将文件放入列表中。希望你能。

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

0

您最初的解决方案几乎是正确的,但是变量“ root”是在递归路径周围动态更新的。os.walk()是一个递归生成器。每个元组集(根,subFolder,文件)都是按照设置方式针对特定根的。

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)

希望这可以帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.