如何在Python中仅用一次OS调用获取目录的所有孙子目录

3 投票
2 回答
935 浏览
提问于 2025-04-17 18:10

我正在尝试在Python中获取某个目录下的所有孙子目录。因为性能原因,我不想在循环中不断调用操作系统的函数(因为这是一个网络文件系统)。这是我目前的做法。有没有更简单的方法可以做到这一点?

dirTree = os.walk(root)
children = [os.path.join(root, x) for x in dirTree.next()[1]]
grandChildren = []
for root, dirs, files in dirTree:
    if root in children:
        for dir in dirs:
            grandChildren.append(os.path.join(root, dir))

补充说明:我不太清楚我调用的os.walk是否是懒加载的。我希望在调用后整个目录树都能在内存中,但我不确定是否真的这样。

2 个回答

1

在POSIX系统和Windows系统中,你不能通过一次操作就获取所有这些数据。至少在POSIX系统中,每个目录需要进行三次操作(opendirreaddirclose),而每个目录项还需要一次额外的操作(stat)。


我认为接下来的方法会比你提供的方案减少操作系统的调用次数。是的,os.walk()的调用是懒惰的;也就是说,调用walk()时,整个目录树并不会全部加载到内存中,而是通过后续调用next()逐步读取。

因此,我的方法只会读取第一层子目录,并且只会对直接的子目录和孙目录进行stat操作。你的方法则会对所有的曾孙目录也进行这样的操作,深度取决于你的目录结构有多复杂。

root='.'
grandChildren = []
for kid in next(os.walk('.'))[1]:
  x = next(os.walk(os.path.join('.', kid)))
  for grandKid in x[1]:  # (or x[1]+x[2] if you care about regular files)
    grandChildren.append(os.path.join(x[0], grandKid))

或者,可以用列表推导式来代替循环:

import os
root='.'
grandChildren = [
  os.path.join(kid, grandKid)
  for kid in next(os.walk(root))[1]
    for grandKid in next(os.walk(os.path.join(root, kid)))[1]]

最后,把os.walk提取到一个函数中:

def read_subdirs(dir='.'):
  import os
  return (os.path.join(dir,x) for x in next(os.walk(dir))[1])

root='.'
grandChildren = [
  grandKid
  for kid in read_subdirs(root)
    for grandKid in read_subdirs(kid)]


通过测试,我们可以看到,如果有曾孙目录,我的方法调用stat的次数明显少于你的方法。

例如,在我的主目录中,我运行了我的代码(/tmp/a.py)和你的代码(/tmp/b.py),在每种情况下root都设置为'.'

$ strace -e stat python /tmp/a.py 2>&1 > /dev/null | egrep -c stat
1245
$ strace -e stat python /tmp/b.py 2>&1 > /dev/null | egrep -c stat
36049
5

如果我理解你的问题没错的话。

你可以使用 glob 这个工具来获取文件或文件夹,只需要用一些通配符就可以了。例如,如果你想要获取 "/home/" 目录下的所有文件夹,可以这样做。

glob.glob('/home/*/*/')

如果你还想知道所有的文件,也可以这样做。

glob.glob('/home/*/*')

撰写回答