由于目录结构庞大且令人费解,我的脚本正在搜索太多目录:
root--
|
--Project A--
|
-- Irrelevant
-- Irrelevant
-- TARGET
|
--Project B--
|
-- Irrelevant
-- TARGET
-- Irrelevant
|
-- Irrelevant --
|
--- Irrelevant
TARGET目录是我唯一需要遍历的目录,并且在每个项目中都有一个一致的名称(在这里我们将其命名为Target)。
我看着这个问题:
排除os.walk中的目录
但是除了排除之外,我需要包括" target"目录,该目录不是" root"级别,而是下一级。
我已经尝试过类似的东西:
def walker(path):
for dirpath, dirnames, filenames in os.walk(path):
dirnames[:] = set(['TARGET'])
但这会影响根目录(从而忽略了它需要遍历的所有目录,Project A,Project B ...)
试试:if TARGET in dirnames: dirnames[:] = [TARGET] ...
使用glob.glob首先获取"目标"子目录的列表,然后在结果上使用os.listdir或os.walk呢?
@CAB这意味着walk不会超过根目录。
@CAB您可能是说else dirnames。 否则,您将结束递归,并且由于根目录不包含TARGET,您最终将无济于事。
您可以只消耗第一个值。 o=os.walk(path) _=next(o)然后输入您的代码
代码的问题在于,您总是在修改dirnames列表,但这意味着即使在根级别,所有子目录也都被删除,因此递归调用最终不会访问各个Project X目录。
您想要的是仅在TARGET存在时清除其他目录:
if 'TARGET' in dirnames:
dirnames[:] = ['TARGET']
这将允许os.walk调用访问Project X目录,但是将阻止它进入Irrelevant目录。
如果初始路径为root,似乎这样将不会执行任何操作,因为它不包含名为TARGET的子目录。
@martineau是的,这是正确的。通过不删除目录名,它确实可以递归到其他目录,这就是答案所在。
啊,对...现在我明白了。好答案。
对于这样的白名单场景,建议使用glob.iglob通过模式获取目录。它是一个生成器,因此您将尽快获得每个结果(注意:在编写本文时,它仍然是使用os.listdir而不是os.scandir来实现的,因此它只是生成器的一半;每个目录会急切地进行扫描,但只有在完成从当前目录产生值后才扫描下一个目录)。例如,在这种情况下:
from future_builtins import filter # Only on Py2 to get generator based filter
import os.path
import glob
from operator import methodcaller
try:
from os import scandir # Built-in on 3.5 and above
except ImportError:
from scandir import scandir # PyPI package on 3.4 and below
# If on 3.4+, use glob.escape for safety; before then, if path might contain glob
# special characters and you don't want them processed you need to escape manually
globpat = os.path.join(glob.escape(path), '*', 'TARGET')
# Find paths matching the pattern, filtering out non-directories as we go:
for targetdir in filter(os.path.isdir, glob.iglob(globpat)):
# targetdir is the qualified name of a single directory matching the pattern,
# so if you want to process the files in that directory, you can follow up with:
for fileentry in filter(methodcaller('is_file'), scandir(targetdir)):
# fileentry is a DirEntry with attributes for .name, .path, etc.
有关更多高级用法,请参阅os.scandir上的文档,或者您可以仅使内部循环调用os.walk来保留大部分原始代码。
如果您确实必须使用os.walk,则可以更轻松地修剪dirs。由于您指定的所有TARGET目录应仅向下一级,因此实际上非常简单。默认情况下,os.walk自上而下,这意味着第一组结果将是根目录(您不希望仅将其修剪到TARGET条目)。因此,您可以执行以下操作:
import fnmatch
for i, (dirpath, dirs, files) in enumerate(os.walk(path)):
if i == 0:
# Top level dir, prune non-Project dirs
dirs[:] = fnmatch.filter(dirs, 'Project *')
elif os.path.samefile(os.path.dirname(dirpath), path):
# Second level dir, prune non-TARGET dirs
dirs[:] = fnmatch.filter(dirs, 'TARGET')
else:
# Do whatever handling you'd normally do for files and directories
# located under path/Project */TARGET/
我是否弄错了,或者如果目录嵌套的层次可能超过一级,则失败吗?并且,如果这没有失败,则意味着它仍将列出Irrelevant目录的内容,并针对其中的每个路径检查其是否验证了模式,但仍然必须浪费时间。
@Bakuriu:如果它们可能嵌套了多个层次,请创建globpat = os.path.join(glob.escape(path), **, TARGET)并进行iglob调用glob.iglob(globpat, recursive=True),它将无限期下降。 OP似乎希望将其精确降低一级,因此不需要深度递归。您也可以调整通配符以避免该单层情况下的Irrelevant目录,例如将*组件替换为Project [AB]进行非常有针对性的选择,或者将Project *替换为任何带有Project前缀的目录。
是的,但是我的意思是,如果您使用**,您的答案将变得无效。 iglob无法知道Irrelevant以下的内容无关紧要,但仍然必须检查所有这些内容(如果Irrelevant包含数百万个文件或嵌套的子目录,则将花费大量时间)。
@Bakuriu:好的。但是然后,OP不需要**。当然,对于在大树中进行深度递归搜索的非常细粒度的过滤器而言,通配很简单,但速度很慢。但是对于OP来说,应该没问题。我确实添加了一个涉及更多的完全过滤os.walk解决方案以提高完整性,该解决方案专门过滤了顶层和第二层,因此它仅完全处理Project *TARGET下的树。但是,如果TARGET可以处于任何深度,这都不会节省您的工作,您仍然会在Project *下的所有内容中寻找TARGET目录。
@Bakuriu:虽然它与此处的单级检查无关,但我确实注意到您想避免glob的一个原因:它遵循符号链接,不能被告知不要这样做。您可能想要这样做,但是它相对较少,并且存在风险(循环引用,或者只是引用了您不想搜索的巨大目录)。 os.walk可以跟随符号链接,但默认情况下不包含。