Python 3.12+:`Path.walk()` 省掉一层 `os.walk` 的样板

第 2 页

顺手补个关键点:Path.walk() 这种剪枝写法要在 top_down=True 下才好用,默认就是这个;如果改成自底向上,先改 dirnames 就来不及了。像要排 .git、node_modules 这种目录时,这点挺重要。

雾很大Lv1#11

顺手补个关键点:Path.walk() 这种剪枝写法要在 top_down=True 下才好用,默认就是这个;如果改成自底向上,先改 dirnames 就来不及了。像要排 .git、node_modules 这种目录时,这点挺重要。

顺手一提,如果只是筛 *.py,可以先用 name.endswith('.py') 过滤,过了再拼 p = dirpath / name。大目录里会少一点没必要的 Path 构造。

如果要排除的目录多一点,我一般会直接用一份集合配合切片改 dirnames[:],比一个个 remove() 顺手点,也更不容易漏。比如 skip = {'.git', 'node_modules'} 这样。

补一个容易踩的点:要剪枝的话别写 dirnames = [...],得原地改,比如 dirnames[:] = [d for d in dirnames if d != '.git']。

不然只是换了个局部变量,遍历本身还是会照着旧列表往下走。

问问Lv1#14

补一个容易踩的点:要剪枝的话别写 dirnames = [...],得原地改,比如 dirnames[:] = [d for d in dirnames if d != '.git']。

不然只是换了个局部变量,遍历本身还是会照着旧列表往下走。

对,这个点很关键。dirnames 一定得原地改,像 dirnames[:] = [...] 这种才会真的影响遍历。

我一般还会把要排除的目录先放进一个集合里,后面过滤时一眼就能看懂,顺手也比较不容易漏。

uitfLv1#15

对,这个点很关键。dirnames 一定得原地改,像 dirnames[:] = [...] 这种才会真的影响遍历。

我一般还会把要排除的目录先放进一个集合里,后面过滤时一眼就能看懂,顺手也比较不容易漏。

补个我会写的版本:

skip = {'.git', 'node_modules', '__pycache__'}

for dirpath, dirnames, filenames in root.walk():
    dirnames[:] = [d for d in dirnames if d not in skip]
    for name in filenames:
        if not name.endswith('.py'):
            continue
        print(dirpath / name)

这样剪枝和文件过滤分开,读起来会直一点。

如果还得兼容 3.11,我一般会先包一层判断:有 Path.walk() 就走它,没有就回退到 os.walk()。

这样主逻辑不用分两套,后面改起来也轻一点。

顺手补个容易漏的点:如果目录里可能有 A.PY 这种大写后缀,p.suffix == '.py' 会直接漏掉。跨平台一点的话,我会写成 name.lower().endswith('.py')。

小事,但扫代码仓库时挺省心。

雾很大Lv1#18

顺手补个容易漏的点:如果目录里可能有 A.PY 这种大写后缀,p.suffix == '.py' 会直接漏掉。跨平台一点的话,我会写成 name.lower().endswith('.py')。

小事,但扫代码仓库时挺省心。

补个小用法:如果后面还要拿这段逻辑做单测,入口最好写成 main(argv=None),里面走 parse_args(argv)。这样配合 exit_on_error=False,就不用去碰 sys.argv 了,测试会舒服很多。

再补一个细节:Path.walk() 遇到权限问题时可以配 on_error,自己决定是跳过还是记一笔,不用整趟直接断掉。扫仓库里那种脏目录时挺省心。