Python中遍历目录树的方法是什么?

55

我认为将文件、文件夹分配并进行+= [item] 的部分有些粗糙。 有什么建议吗?我正在使用Python 3.2

from os import *
from os.path import *

def dir_contents(path):
    contents = listdir(path)
    files = []
    folders = []
    for i, item in enumerate(contents):
        if isfile(contents[i]):
            files += [item]
        elif isdir(contents[i]):
            folders += [item]
    return files, folders

30
避免使用from x import *。这是一条有关Python风格的建议。 - Chris Morgan
1
这种向列表添加项目的方式也很粗糙。使用files.append(item)添加单个项目或使用files.extend([item1, item2, ...])添加多个项目。 - Ronan Paixão
17个回答

53

os.walkos.scandir都是不错的选择,但是我越来越多地使用pathlib,而且使用pathlib您可以使用.glob().rglob()(递归glob)方法:

root_directory = Path(".")
for path_object in root_directory.rglob('*'):
    if path_object.is_file():
        print(f"hi, I'm a file: {path_object}")
    elif path_object.is_dir():
        print(f"hi, I'm a dir: {path_object}")



6
然而,os.walk 已经为您分离了文件和目录。另外,我刚想起来:使用 os.walk,如果我将 topdown 设置为 True(默认值),我可以操作子目录列表,例如跳过整个子树。请参阅文档中有关大型树中 ** 的注释。我希望 os.walk 能够返回 Path 对象。(愚蠢的 5 分钟编辑限制) - Jürgen A. Erhard
1
你可以用rglob('*')代替glob('**/*'),这样看起来更优雅些。 - Paul

45

查看os.walk函数,该函数返回路径及其包含的目录和文件。这将大大缩短您的解决方案。


1
哇,太完美了,不敢相信我居然错过了。谢谢你。 - Mike
3
但是 os.walk 并不像 OP 的代码一样仅限于一个目录层级。 - Dan D.

39

如果有人想使用pathlibpython >= 3.4)来解决问题

from pathlib import Path

def walk(path): 
    for p in Path(path).iterdir(): 
        if p.is_dir(): 
            yield from walk(p)
            continue
        yield p.resolve()

# recursively traverse all files from current directory
for p in walk(Path('.')): 
    print(p)

# the function returns a generator so if you need a list you need to build one
all_files = list(walk(Path('.'))) 

然而,正如上面提到的那样,这不会保留os.walk所给出的自上而下的顺序。


5
我之前从未见过yield from这个语法,或者至少我已经忘记了。感谢您在这里进行说明!为后人提供相关文档:https://docs.python.org/3/whatsnew/3.3.html#pep-380 - David Marx
请注意,此代码实现的方式意味着只会列出文件,而不是目录。 - Flimm
我认为不需要使用 continue 语句;即使没有它,我也可以得到相同的结果。 - AllanLRH
如果排除continue语句,它还会提供目录。否则你只能得到文件。所以这取决于你想要什么。 - user3645016

9
自从 Python >= 3.4 开始,就有了生成器方法 Path.rglob。因此,要处理 some/starting/path 下的所有路径,只需执行类似以下操作:
from pathlib import Path

path = Path('some/starting/path') 
for subpath in path.rglob('*'):
    # do something with subpath

要将所有子路径获取到列表中,请执行 list(path.rglob('*'))。 要仅获取具有 sql 扩展名的文件,请执行 list(path.rglob('*.sql'))


从现在开始,我将在所有地方都使用它。遗憾的是,Python开发人员没有将第一个参数默认设置为“*”,否则它可能会更短:) 此外,如果需要仅获取目录,则可以通过传递空字符串给rglob来实现。 - Keeely

4
如果你想递归遍历所有文件,包括子文件夹中的所有文件,我认为这是最好的方式。
import os

def get_files(input):
    for fd, subfds, fns in os.walk(input):
       for fn in fns:
            yield os.path.join(fd, fn)

## now this will print all full paths

for fn in get_files(fd):
    print(fn)

3
我很喜欢这个方法,因为它将文件系统迭代代码与处理每个文件的代码分开!不过,“yield from”这行需要省略 — os.walk 已经遍历了子目录,所以如果你也这么做,就会看到子目录中的文件2^n次。 - Alex Martini

4

使用pathlib模块遍历目录树的另一种解决方案:

from pathlib import Path

for directory in Path('.').glob('**'):
    for item in directory.iterdir():
        print(item)

**模式递归匹配当前目录及其所有子目录,并使用iterdir方法迭代每个目录的内容。在遍历目录树时需要更多控制时非常有用。


3
def dir_contents(path):
    files,folders = [],[]
    for p in listdir(path):
        if isfile(p): files.append(p)
        else: folders.append(p)
    return files, folders

3

确实使用

items += [item]

有很多原因使得 is bad...

  1. append 方法就是为了将一个元素添加到列表的末尾而设计的。

  2. 你正在创建一个临时列表,只是为了扔掉它。虽然在使用 Python 时,原始速度不应该是你的首要关注点(否则你正在使用错误的语言),但是没有理由浪费速度。

  3. 你正在使用 Python 语言的一些不对称之处... 对于列表对象,写入 a += b 并不等同于写入 a = a + b,因为前者会直接修改对象,而后者则会分配一个新的列表,如果对象 a 也可以通过其他方式访问,则可能具有不同的语义。 在你的特定代码中,这似乎不是问题,但是当其他人(或几年后的你)需要修改代码时,这可能成为一个问题。Python 甚至还有一个名为 extend 的方法,具有更不明显的语法,专门用于处理在列表对象末尾添加另一个列表的元素以原地修改列表对象的情况。

此外,正如其他人所指出的那样,你的代码似乎正在尝试做 os.walk 已经做过的事情...


3
自 Python 3.4 版本起,新增了一个名为 pathlib 的模块。要获取所有目录和文件,可以执行以下操作:
from pathlib import Path

dirs = [str(item) for item in Path(path).iterdir() if item.is_dir()]
files = [str(item) for item in Path(path).iterdir() if item.is_file()]

14
iterdir() 不会递归遍历整个目录树。 - Brian
4
但是... pathlib支持递归式的globbing操作。 - kojiro
1
方法iterdir()不能保证os.walk()自上而下的顺序。我非常不愿意尝试重新实现那个经过验证的方法。(注意:一些方法,如os.rmdir()只能删除空目录,因此顺序非常重要。) - ingyhere

1

我使用了一些源自其他地方建议的代码,而不是内置的os.walk和os.path.walk。最初我曾经链接到这段代码,但现在已经用内联源代码替换了它。

import os
import stat

class DirectoryStatWalker:
    # a forward iterator that traverses a directory tree, and
    # returns the filename and additional file information

    def __init__(self, directory):
        self.stack = [directory]
        self.files = []
        self.index = 0

    def __getitem__(self, index):
        while 1:
            try:
                file = self.files[self.index]
                self.index = self.index + 1
            except IndexError:
                # pop next directory from stack
                self.directory = self.stack.pop()
                self.files = os.listdir(self.directory)
                self.index = 0
            else:
                # got a filename
                fullname = os.path.join(self.directory, file)
                st = os.stat(fullname)
                mode = st[stat.ST_MODE]
                if stat.S_ISDIR(mode) and not stat.S_ISLNK(mode):
                    self.stack.append(fullname)
                return fullname, st

if __name__ == '__main__':
    for file, st in DirectoryStatWalker("/usr/include"):
        print file, st[stat.ST_SIZE]

它可以递归地遍历目录,非常高效且易于阅读。


+1 @mikebabcock 谢谢 - 这个对我来说在 Python 2.x 中可以直接使用(尽管 OP 使用的是 3.x),我需要一个 2.x 的解决方案。 - therobyouknow
很遗憾,该项目已不再可用,出现了404错误。有人能否在此重新粘贴一下? - LarsH
1
我还没有检查它是否完全相同,但请参考http://pymoex.googlecode.com/svn/trunk/os_path/directoryStatWalker.py @LarsH。 - mikebabcock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接