Python 递归文件夹读取
- 2025-02-10 08:56:00
- admin 原创
- 54
问题描述:
我有 C++/Obj-C 背景,刚刚开始学习 Python(写了大约一个小时)。我正在编写一个脚本,以递归方式读取文件夹结构中的文本文件的内容。
我遇到的问题是,我编写的代码仅适用于一个文件夹深度。我可以从代码中看到原因(请参阅#hardcoded path
),但我不知道如何使用 Python,因为我对它的经验才刚刚开始。
Python代码:
import os
import sys
rootdir = sys.argv[1]
for root, subFolders, files in os.walk(rootdir):
for folder in subFolders:
outfileName = rootdir + "/" + folder + "/py-outfile.txt" # hardcoded path
folderOut = open( outfileName, 'w' )
print "outfileName is " + outfileName
for file in files:
filePath = rootdir + '/' + file
f = open( filePath, 'r' )
toWrite = f.read()
print "Writing '" + toWrite + "' to" + filePath
folderOut.write( toWrite )
f.close()
folderOut.close()
解决方案 1:
确保您理解以下三个返回值os.walk
:
for root, subdirs, files in os.walk(rootdir):
含义如下:
root
:当前路径为“已走过”subdirs
:目录中的文件root
类型files
:位于 中root
(不在 中subdirs
)且类型不是目录的文件
请使用os.path.join
而不是斜杠连接!您的问题是filePath = rootdir + '/' + file
- 您必须连接当前“遍历”的文件夹而不是最顶层的文件夹。所以必须是filePath = os.path.join(root, file)
。顺便说一句,“文件”是内置的,因此您通常不会将其用作变量名。
另一个问题是您的循环,它应该是这样的,例如:
import os
import sys
walk_dir = sys.argv[1]
print('walk_dir = ' + walk_dir)
# If your current working directory may change during script execution, it's recommended to
# immediately convert program arguments to an absolute path. Then the variable root below will
# be an absolute path as well. Example:
# walk_dir = os.path.abspath(walk_dir)
print('walk_dir (absolute) = ' + os.path.abspath(walk_dir))
for root, subdirs, files in os.walk(walk_dir):
print('--
root = ' + root)
list_file_path = os.path.join(root, 'my-directory-list.txt')
print('list_file_path = ' + list_file_path)
with open(list_file_path, 'wb') as list_file:
for subdir in subdirs:
print(' - subdirectory ' + subdir)
for filename in files:
file_path = os.path.join(root, filename)
print(' - file %s (full path: %s)' % (filename, file_path))
with open(file_path, 'rb') as f:
f_content = f.read()
list_file.write(('The file %s contains:
' % filename).encode('utf-8'))
list_file.write(f_content)
list_file.write(b'
')
如果您不知道的话,with
文件的语句是一种简写:
with open('filename', 'rb') as f:
dosomething()
# is effectively the same as
f = open('filename', 'rb')
try:
dosomething()
finally:
f.close()
解决方案 2:
如果您使用的是 Python 3.5 或更高版本,则可以用一行完成此操作。
import glob
# root_dir needs a trailing slash (i.e. /root/dir/)
for filename in glob.iglob(root_dir + '**/*.txt', recursive=True):
print(filename)
正如文档中提到的
如果递归为真,则模式“**”将匹配任何文件以及零个或多个目录和子目录。
如果你想要每个文件,你可以使用
import glob
for filename in glob.iglob(root_dir + '**/**', recursive=True):
print(filename)
解决方案 3:
同意 Dave Webb 的观点,os.walk
将为树中的每个目录生成一个项目。事实上,您不必关心subFolders
。
像这样的代码应该可以工作:
import os
import sys
rootdir = sys.argv[1]
for folder, subs, files in os.walk(rootdir):
with open(os.path.join(folder, 'python-outfile.txt'), 'w') as dest:
for filename in files:
with open(os.path.join(folder, filename), 'r') as src:
dest.write(src.read())
解决方案 4:
TL;DR:这些相当于find -type f
,遍历下面所有文件夹中的所有文件(包括当前文件夹):
folder = '.'
import os
for currentpath, folders, files in os.walk(folder):
for file in files:
print(os.path.join(currentpath, file))
## or:
import glob
for pathstr in glob.iglob(glob.escape(folder) + '/**/*', recursive=True):
print(pathstr)
比较两种方法:
os.walk
速度快 3 倍os.walk
在我的测试中,使用了稍微多一点的内存,因为files
数组保存了 82k 个条目,而glob
返回一个迭代器并流式传输结果。对每个结果的逐个处理(更多调用和更少缓冲)可能解释了速度差异如果你忘记了
i
inglob.iglob()
,它将返回一个列表而不是迭代器,并且可能会使用更多的内存
os.walk
不会默默地给出不完整的结果或意外地将名称解释为匹配模式glob
不显示空目录glob
需要使用转义目录和文件名,glob.escape(name)
因为它们可能包含特殊字符glob
排除以点开头的目录和文件(例如,~/.bashrc
或~/.vim
)并且include_hidden
不能解决这个问题(它只包括隐藏文件夹;您需要为点文件指定第二种模式)glob
没有告诉你什么是文件,什么是目录glob
进入符号链接,可能会导致你列举出完全不同位置的大量文件(这可能是你想要的;在这种情况下,os.walk
作为followlinks=True
一个选项)os.walk
让你可以在运行时通过修改文件夹数组来修改要走的路径,不过个人感觉这有点混乱,我不确定我是否会推荐这样做
其他答案已经提到了os.walk()
,但可以解释得更好。这很简单!让我们来看看这棵树:
docs/
└── doc1.odt
pics/
todo.txt
使用此代码:
for currentpath, folders, files in os.walk('.'):
print(currentpath)
这currentpath
是它正在查看的当前文件夹。这将输出:
.
./docs
./pics
因此它会循环三次,因为有三个文件夹:当前文件夹、docs
和pics
。在每次循环中,它会用所有文件夹和文件填充变量folders
和files
。让我们展示它们:
for currentpath, folders, files in os.walk('.'):
print(currentpath, folders, files)
这告诉我们:
# currentpath folders files
. ['pics', 'docs'] ['todo.txt']
./pics [] []
./docs [] ['doc1.odt']
因此在第一行中,我们看到我们在文件夹中.
,它包含两个文件夹,即pics
和docs
,并且有一个文件,即todo.txt
。您无需执行任何操作即可递归到这些文件夹中,因为如您所见,它会自动递归并只为您提供任何子文件夹中的文件。以及该文件夹中的任何子文件夹(尽管我们在示例中没有这些子文件夹)。
如果你只是想循环遍历所有文件,相当于find -type f
,你可以这样做:
for currentpath, folders, files in os.walk('.'):
for file in files:
print(os.path.join(currentpath, file))
输出:
./todo.txt
./docs/doc1.odt
解决方案 5:
这个pathlib
库非常适合处理文件。你可以Path
像这样对对象进行递归操作。
from pathlib import Path
for elem in Path('/path/to/my/files').rglob('*.*'):
print(elem)
解决方案 6:
我发现以下是最简单的
from glob import glob
import os
files = [f for f in glob('rootdir/**', recursive=True) if os.path.isfile(f)]
使用glob('some/path/**', recursive=True)
获取所有文件,但也包括目录名称。添加if os.path.isfile(f)
条件可将此列表过滤为仅现有文件
解决方案 7:
import glob
import os
root_dir = <root_dir_here>
for filename in glob.iglob(root_dir + '**/**', recursive=True):
if os.path.isfile(filename):
with open(filename,'r') as file:
print(file.read())
**/**
用于递归获取所有文件,包括directory
。
if os.path.isfile(filename)
用于检查filename
变量是否为file
或directory
,如果是文件,那么我们可以读取该文件。这里我正在打印文件。
解决方案 8:
如果你想要一个给定目录下所有路径的简单列表(比如find .
在 shell 中):
files = [
os.path.join(parent, name)
for (parent, subdirs, files) in os.walk(YOUR_DIRECTORY)
for name in files + subdirs
]
如果仅包含基目录下文件的完整路径,请省略+ subdirs
。
解决方案 9:
对我来说,os.walk()
这有点太复杂和冗长了。您可以通过以下方式更清晰地理解已接受的答案:
all_files = [str(f) for f in pathlib.Path(dir_path).glob("**/*") if f.is_file()]
with open(outfile, 'wb') as fout:
for f in all_files:
with open(f, 'rb') as fin:
fout.write(fin.read())
fout.write(b'
')
解决方案 10:
用来os.path.join()
构建你的路径-它更整洁:
import os
import sys
rootdir = sys.argv[1]
for root, subFolders, files in os.walk(rootdir):
for folder in subFolders:
outfileName = os.path.join(root,folder,"py-outfile.txt")
folderOut = open( outfileName, 'w' )
print "outfileName is " + outfileName
for file in files:
filePath = os.path.join(root,file)
toWrite = open( filePath).read()
print "Writing '" + toWrite + "' to" + filePath
folderOut.write( toWrite )
folderOut.close()
解决方案 11:
如果仅有文件名还不够的话,可以很容易地在其上实现深度优先搜索os.scandir()
:
stack = ['.']
files = []
total_size = 0
while stack:
dirname = stack.pop()
with os.scandir(dirname) as it:
for e in it:
if e.is_dir():
stack.append(e.path)
else:
size = e.stat().st_size
files.append((e.path, size))
total_size += size
文档中有这样的说法:
scandir() 函数返回目录条目以及文件属性信息,为许多常见用例提供更好的性能。
解决方案 12:
os.walk
默认进行递归遍历。对于每个目录,从根目录开始,它会产生一个 3 元组 (dirpath、dirnames、filenames)
from os import walk
from os.path import splitext, join
def select_files(root, files):
"""
simple logic here to filter out interesting files
.py files in this example
"""
selected_files = []
for file in files:
#do concatenation here to get full path
full_path = join(root, file)
ext = splitext(file)[1]
if ext == ".py":
selected_files.append(full_path)
return selected_files
def build_recursive_dir_tree(path):
"""
path - where to begin folder scan
"""
selected_files = []
for root, dirs, files in walk(path):
selected_files += select_files(root, files)
return selected_files
解决方案 13:
如果你更喜欢(几乎)Oneliner:
from pathlib import Path
lookuppath = '.' #use your path
filelist = [str(item) for item in Path(lookuppath).glob("**/*") if Path(item).is_file()]
在这种情况下,您将获得一个列表,其中仅包含所有文件在 lookuppath 下递归查找的路径。如果没有 str(),您将获得添加到每个路径的 PosixPath()。
解决方案 14:
我认为问题在于您没有正确处理输出os.walk
。
首先,改变:
filePath = rootdir + '/' + file
到:
filePath = root + '/' + file
rootdir
是您的固定起始目录;root
是 返回的目录os.walk
。
其次,您不需要缩进文件处理循环,因为对每个子目录运行此循环毫无意义。您将设置root
到每个子目录。除非您想对目录本身进行处理,否则您不需要手动处理子目录。
解决方案 15:
尝试一下:
import os
import sys
for root, subdirs, files in os.walk(path):
for file in os.listdir(root):
filePath = os.path.join(root, file)
if os.path.isdir(filePath):
pass
else:
f = open (filePath, 'r')
# Do Stuff
解决方案 16:
这对我有用:
import glob
root_dir = "C:\\Users\\Scott\\\" # Don't forget trailing (last) slashes
for filename in glob.iglob(root_dir + '**/*.jpg', recursive=True):
print(filename)
# do stuff
解决方案 17:
从 Python 3.12开始,你也可以使用walk()
frompathlib
类似于os.walk()
,但会生成(dirpath, dirnames, filenames)
where dirpath
is a的元组Path
。例如:
from pathlib import Path
for root, dirs, files in Path("cpython/Lib/concurrent").walk(on_error=print):
print(
root,
"consumes",
sum((root / file).stat().st_size for file in files),
"bytes in",
len(files),
"non-directory files"
)
if '__pycache__' in dirs:
dirs.remove('__pycache__')