使用 os.walk 定位目录

Targeting a directory with os.walk

由于目录结构庞大而复杂,我的脚本搜索了太多目录:

root--
     |
     --Project A--
                  |
                  -- Irrelevant
                  -- Irrelevant
                  -- TARGET
     |
     --Project B--
                  |
                  -- Irrelevant
                  -- TARGET
                  -- Irrelevant
     |
     -- Irrelevant  --
                       |
                       --- Irrelevant

TARGET目录是我唯一需要遍历的目录,它在每个项目中都有一个一致的名称(我们这里就叫它Target)。

我看了这个问题:

Excluding directories in os.walk

但不是排除,我需要包含 "target" 目录,它不在 "root" 级别,而是下一级。

我试过类似的东西:

def walker(path):
    for dirpath, dirnames, filenames in os.walk(path):
        dirnames[:] = set(['TARGET'])

但是这个影响了根目录(因此忽略了它需要遍历的所有目录,项目A,项目B...)

对于像这样的白名单场景,我建议使用 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/

您的代码的问题是您 总是 修改 dirnames 列表,但这意味着即使在根级别,所有子目录都被删除,因此递归调用最终不会访问各种 Project X 目录。

你想要的是清除其他目录TARGET存在时:

if 'TARGET' in dirnames:
    dirnames[:] = ['TARGET']

这将允许 os.walk 调用访问 Project X 目录,但会阻止它进入 Irrelevant 目录。