如何进行递归子文件夹搜索并返回列表中的文件?
- 2025-01-09 08:46:00
- admin 原创
- 87
问题描述:
我正在编写一个脚本,用于递归遍历主文件夹中的子文件夹并根据特定文件类型构建列表。我在使用该脚本时遇到了问题。当前设置如下:
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 循环,然后加入路径的第一部分,但我想在那之前再检查一下是否有人有什么建议。
解决方案 1:
您应该使用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 3.4+ 的 Edit2
from pathlib import Path
result = list(Path(".").rglob("*.[tT][xX][tT]"))
解决方案 2:
在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=True):
# ...
解决方案 3:
这似乎是我能想到的最快的解决方案,并且比任何解决方案都快os.walk
,而且快得多glob
。
它还将免费为您提供所有嵌套子文件夹的列表。
您可以搜索几种不同的扩展。
您还可以选择返回完整路径或仅返回文件的名称,方法是更改
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")
速度分析
获取所有子文件夹和主文件夹内具有特定文件扩展名的所有文件的各种方法。
总结:
fast_scandir
显然获胜了,并且比除 os.walk 之外的所有其他解决方案快两倍。os.walk
排名第二的则稍慢一些。使用
glob
将大大减慢该过程。所有结果均未使用自然排序。这意味着结果将按如下方式排序:1、10、2。要获得自然排序 (1、2、10),请查看:
结果:
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
更新时间:2022-07-20(Py3.10.1
正在寻找*.pdf
)
glob.iglob took 132 ms. Found files: 9999
glob.glob took 134 ms. Found files: 9999
fast_scandir took 331 ms. Found files: 9999. Found subfolders: 9330
os.walk took 695 ms. Found files: 9999
pathlib.rglob took 828 ms. Found files: 9999
find_files took 949 ms. Found files: 9999
os.walk-glob took 1242 ms. Found files: 9999
使用 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 took {(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 took {(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 took {(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 took {(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 took {(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 took {(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 took {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(files)}. Found subfolders: {len(subf)}")
解决方案 4:
我将把John La Rooy 的列表推导翻译为嵌套的 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)
这是列表推导和函数os.walk和glob.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:
您的原始解决方案几乎是正确的,但变量“root”在递归路径中会动态更新。os.walk() 是一个递归生成器。每个元组集 (root、subFolder、files) 都针对特定的根,就像您设置的那样。
IE
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 变量,因为它实际上是"root" 中的所有子文件夹。因此,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)
解决方案 7:
这不是最符合 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']
解决方案 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))
return files
if __name__ == '__main__':
result = list_files_recursive('/tmp')
print(result)
解决方案 9:
递归是 Python 3.5 中的新功能,因此它不适用于 Python 2.7。下面是使用r
字符串的示例,因此您只需在 Win、Lin 上提供路径即可……
import glob
mypath=r"C:UsersdjDesktop
ba"
files = glob.glob(mypath + r'***.py', recursive=True)
# print(files) # as list
for f in files:
print(f) # nice looking single line per file
注意:它将列出所有文件,无论深度如何。
解决方案 10:
如果您不介意安装额外的灯光库,您可以这样做:
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
。
解决方案 11:
此函数将递归地将文件放入列表中。
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
', 'Error: ', err)
return files
解决方案 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))