获取当前目录下所有子目录的列表

947

有没有办法在Python中返回当前目录中所有子目录的列表?

我知道可以获取文件列表,但我需要获取目录列表。


4
https://docs.python.org/3.4/library/os.html?highlight=os#os.listdirhttps://docs.python.org/3.4/library/os.path.html#os.path.isdir - The Demz
23
如果您正在寻找pathlib解决方案,请使用[f for f in data_path.iterdir() if f.is_dir()]。这将返回文件夹名称作为字符串,还会自动排除...,非常感谢。另外,glob.glob解决方案也很有价值:glob.glob("/path/to/directory/*/") - Charlie Parker
35个回答

926

你是指立即子目录还是整个树下的每个目录?无论哪种方式,你都可以使用os.walk来实现:

os.walk(directory)

将为每个子目录生成一个元组。3-tuple中的第一个条目是目录名称,因此

[x[0] for x in os.walk(directory)]

这应该会递归地给你所有的子目录。

请注意,元组中第二个条目是第一个位置的入口的子目录列表,所以你可以使用它代替,但这不太可能节省很多时间。

但是,你可以使用它来仅获取直接子目录:

next(os.walk('.'))[1]
或者查看已经发布的其他解决方案,使用 os.listdiros.path.isdir,包括在 "如何在Python中获取所有直接子目录" 中的那些解决方案。

3
这是一个干净而优美的答案。谢谢。我不熟悉next(),认为这个链接可能对处于类似情况的人有所帮助:https://dev59.com/HnI-5IYBdhLWcg3wq6do - Helene
45
如果有人担心os.walkos.listdir + os.path.isdir方法之间的性能差异:我刚在一个有10,000个子目录(包含数百万个文件)的目录上进行了测试,性能差异可以忽略不计。os.walk方法: "10次循环,每次44.6毫秒" 和 os.listdir+os.path.isdir方法:"10次循环,每次45.1毫秒"。 - kevinmicke
5
@kevinmicke 尝试在网络驱动器上运行这个性能测试,我想你会发现在这种情况下性能非常显著。 - UKMonkey
@UKMonkey:实际上,在3.4和更早的版本中,它们应该大致相同,在3.5及更高版本中,os.walk应该击败os.listdir + os.path.isdir尤其是在网络驱动器上。原因:1)os.walk是惰性的;如果您执行next(os.walk('.'))[1],它将执行单个目录列表并按目录/非目录进行分类,然后消失。设置生成器的成本是非零的,但与文件系统访问的成本完全无关。2)从3.5开始,os.walk通过os.scandir实现,它不需要每个条目的stat调用来对目录/非目录进行分类(除符号链接外)... - ShadowRanger
os.walk会输给正确使用的os.scandir(例如[e.path for e in os.scandir('.') if e.is_dir()]这样极简),但只是因为它有一些额外的包装开销,以允许递归和单独存储非目录list,但这两者都没有被用到; 它不执行任何其他的系统调用工作,所以无论是网络驱动器还是其他情况,费用中最大的部分(驱动器延迟,稍微小一些的系统调用开销)仍然比3.5版本以上的os.listdir+os.path.isdir更便宜。 - ShadowRanger
显示剩余2条评论

320

你可以直接使用 glob.glob

from glob import glob
glob("/path/to/directory/*/", recursive = True)

*后面不要忘记加上尾随的/


2
好���。简单明了。只是它在名称中保留了尾部的“/”。 - juanmirocks
31
如果您不能假定 / 为文件夹分隔符,请执行以下操作:glob(os.path.join(path_to_directory, "*", "")) - juanmirocks
8
这对于子目录无效!要使用glob,这是完整的答案:在Python中递归地使用Glob()查找文件? - poppie
6
要使全局递归,您只需添加以下参数 recursive=True - JacoSolari
不适用于子目录。 - KansaiRobot
使用2个星号可以适用于子目录:glob("/path/to/directory/**/", recursive=True) - Ludo Schmidt

312

这种方法比上面的方法更好,因为你不需要多次使用os.path.join()函数,而且你可以直接获取完整路径(如果你愿意),在Python 3.5及以上版本中可以使用。

subfolders = [ f.path for f in os.scandir(folder) if f.is_dir() ]

这将提供子目录的完整路径。如果您只想要子目录的名称,可以使用 f.name 而不是 f.path https://docs.python.org/3/library/os.html#os.scandir
略微偏离主题: 如果您需要递归地获取所有子文件夹和/或所有文件,请查看这个函数,它比 os.walkglob 更快,并将返回所有子文件夹中及其子文件夹中的所有文件的列表: https://dev59.com/OmMl5IYBdhLWcg3wcmvD#59803793 如果您只想要递归地获取所有子文件夹
def fast_scandir(dirname):
    subfolders= [f.path for f in os.scandir(dirname) if f.is_dir()]
    for dirname in list(subfolders):
        subfolders.extend(fast_scandir(dirname))
    return subfolders

返回所有子文件夹及其完整路径的列表。这比os.walk快得多,比glob快得多。

所有函数的分析

tl;dr:
- 如果你想获取一个文件夹的所有直接子目录,请使用os.scandir
- 如果你想获取所有子目录,甚至是嵌套的目录,请使用os.walk或 - 稍微快一点 - 上面的fast_scandir函数。
- 永远不要只使用os.walk来获取顶级子目录,因为它可能比os.scandir慢数百倍!

  • 如果您运行下面的代码,请确保运行一次以使您的操作系统访问了该文件夹,然后丢弃结果并运行测试,否则结果将会出错。
  • 您可能想混合使用函数调用,但我已经测试过了,它并不重要。
  • 所有示例都将给出文件夹的完整路径。pathlib示例作为(Windows)Path对象。
  • os.walk的第一个元素将是基础文件夹。因此,您将不仅获得子目录。您可以使用fu.pop(0)来删除它。
  • 没有任何结果将使用自然排序。这意味着结果将按照以下方式排序:1、10、2。要获得自然排序(1、2、10),请查看https://dev59.com/Gm445IYBdhLWcg3wia2V#48030307


结果

os.scandir      took   1 ms. Found dirs: 439
os.walk         took 463 ms. Found dirs: 441 -> it found the nested one + base folder.
glob.glob       took  20 ms. Found dirs: 439
pathlib.iterdir took  18 ms. Found dirs: 439
os.listdir      took  18 ms. Found dirs: 439

测试过适用于W7x64,Python 3.8.1。
# -*- coding: utf-8 -*-
# Python 3


import time
import os
from glob import glob
from pathlib import Path


directory = r"<insert_folder>"
RUNS = 1


def run_os_walk():
    a = time.time_ns()
    for i in range(RUNS):
        fu = [x[0] for x in os.walk(directory)]
    print(f"os.walk\t\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found dirs: {len(fu)}")


def run_glob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = glob(directory + "/*/")
    print(f"glob.glob\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found dirs: {len(fu)}")


def run_pathlib_iterdir():
    a = time.time_ns()
    for i in range(RUNS):
        dirname = Path(directory)
        fu = [f for f in dirname.iterdir() if f.is_dir()]
    print(f"pathlib.iterdir\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found dirs: {len(fu)}")


def run_os_listdir():
    a = time.time_ns()
    for i in range(RUNS):
        dirname = Path(directory)
        fu = [os.path.join(directory, o) for o in os.listdir(directory) if os.path.isdir(os.path.join(directory, o))]
    print(f"os.listdir\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found dirs: {len(fu)}")


def run_os_scandir():
    a = time.time_ns()
    for i in range(RUNS):
        fu = [f.path for f in os.scandir(directory) if f.is_dir()]
    print(f"os.scandir\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms.\tFound dirs: {len(fu)}")


if __name__ == '__main__':
    run_os_scandir()
    run_os_walk()
    run_glob()
    run_pathlib_iterdir()
    run_os_listdir()

2
在你提出问题时,如果能早点说明你替换了哪些函数,那就更好了。不过无论如何,你投入时间做这件事真的令人印象深刻。干得好!我个人更喜欢使用单个库,所以我喜欢按照以下方式使用pathlib[f for f in p.iterdir() if f.is_dir()] - Charlie Parker
我有50个子目录,每个子目录下都有数千个子目录。我刚刚尝试运行fast_scandir,但它需要一个多小时的时间。这正常吗?有什么方法可以加快速度吗? - Vincent
非常清晰聪明的回答。谢谢! - Cyryl1972
这是一个很好的解释。 - heySushil
这是此页面上最佳的答案。列表推导式快速且代码最少!!!性能很重要!!! - Rich Lysakowski PhD

214
import os

d = '.'
[os.path.join(d, o) for o in os.listdir(d) 
                    if os.path.isdir(os.path.join(d,o))]

6
请注意,在这种方法中,如果不在“.”上执行,则需要注意绝对路径问题。 - daspostloch
5
提醒一下,如果你没有使用当前工作目录('.'),那么除非在 o 上做一个 os.path.join 获取完整路径,否则 isdir(0) 将始终返回 false。 - James McMahon
8
看起来这篇文章已经更新,修复了上述提到的两个问题。 - cgmb
3
为避免两次调用os.path.join,您可以先使用 os.path.join 拼接路径,然后再使用 os.path.isdir 过滤列表:filter(os.path.isdir, [os.path.join(d, o) for o in os.listdir(d)]) - quant_dev
2
使用pathlib与[f for f in data_path.iterdir() if f.is_dir()]或glob一起使用更简单易读:glob.glob("/path/to/directory/*/") - Charlie Parker

85
Python 3.4引入pathlib模块到标准库中,它提供了一种面向对象的处理文件系统路径的方法:
from pathlib import Path

p = Path('./')

# All subdirectories in the current directory, not recursive.
[f for f in p.iterdir() if f.is_dir()]

为了递归列出所有子目录,可以使用路径通配符和**模式。
# This will also include the current directory '.'
list(p.glob('**'))

请注意,单个通配符*会包括非递归的文件和目录。要仅获取目录,请添加尾随/,但这仅适用于直接使用glob库时,而不是通过pathlib使用glob库时。
import glob

# These three lines return both files and directories
list(p.glob('*'))
list(p.glob('*/'))
glob.glob('*')

# Whereas this returns only directories
glob.glob('*/')

所以Path('./').glob('**')glob.glob('**/', recursive=True)匹配的路径相同。
通过PyPi上的pathlib2模块,Pathlib也可在Python 2.7上使用。

要遍历子目录列表,这里有一个漂亮、干净的语法:for f in filter(Path.is_dir, p.iterdir()): - Bryan Roach
你确定你的glob解决方案需要两个星号吗? glob(*/) 不足以满足需求吗?无论如何,这是一个绝妙的答案,特别是你干净利落地使用了 pathlib。如果它也允许递归,那么评论一下会很好,尽管从问题的标题来看这不是必需的,未来的读者应该阅读你链接的文档。 - Charlie Parker
2
谢谢@CharlieParker!我更新了我的答案,详细介绍了递归和使用尾随斜杠的细节(包括指出在使用pathlib的glob时,使用**不需要尾随斜杠)。关于使用单个星号,这将非递归地匹配文件和目录。 - joelostblom
glob.glob('**/', recursive=True) 不会包含隐藏目录,但是 Path('./').glob('**') 会。 - nos
可能需要在开头添加一个 sorted(),以便返回的列表是排序的...根据使用情况可能有用也可能没用。 - Matias Andina

44
如果你需要一种递归解决方案来查找所有子目录中的子目录,请像之前提出的那样使用“walk”函数。
如果您只需要当前目录的子目录,请结合“os.listdir”和“os.path.isdir”使用。

8
使用pathlib更简单:[f for f in p.iterdir() if f.is_dir()] - Charlie Parker
3
@CharlieParker: 这个答案比 pathlib 出现早几年。 - Eli Bendersky
为什么不在一行中写出完整的答案? - Jürgen K.

35

列出仅目录

print("\nWe are listing out only the directories in current directory -")
directories_in_curdir = list(filter(os.path.isdir, os.listdir(os.curdir)))
print(directories_in_curdir)

仅列出当前目录中的文件

files = list(filter(os.path.isfile, os.listdir(os.curdir)))
print("\nThe following are the list of all files in the current directory -")
print(files)

9
在mac OS上无法运行。我认为问题在于os.listdir只返回目录的名称而不是完整路径,但os.path.isdir仅在完整路径为目录时返回True。 - denson
5
如果您稍微修改一下这行代码,它就可以在当前目录之外运行: subdirs = filter(os.path.isdir, [os.path.join(dir,x) for x in os.listdir(dir)])。将目录(dir)和文件名(x)连接起来,并筛选出是目录的部分。 - RLC
避免定义lambda函数,直接传递函数是一个不错的做法。 - Charlie Parker
幸运的是,在Mac OS X上,你可以通过在过滤器链之外调用isdir来解决这个问题。 - Sridhar Sarnobat
2023 年以后,可以在 M1 Mac 上工作。谢谢! - BLimitless

29

1
使用 pathlib 更简单:[f for f in p.iterdir() if f.is_dir()] - Charlie Parker

25

使用python-os-walk实现。(http://www.pythonforbeginners.com/code-snippets-source-code/python-os-walk/)

import os

print("root prints out directories only from what you specified")
print("dirs prints out sub-directories from root")
print("files prints out all files from root and directories")
print("*" * 20)

for root, dirs, files in os.walk("/var/log"):
    print(root)
    print(dirs)
    print(files)

使用 pathlib 更简单:[f for f in p.iterdir() if f.is_dir()] - Charlie Parker

18

您可以使用Python 2.7中的os.listdir(path)获取子目录(和文件)列表。

import os
os.listdir(path)  # list of subdirectories and files

70
这也包括文件。 - Tarnay Kálmán
3
名称有些令人困惑,因为“dir”并不指代列表中的对象,而是指代包含这个列表的目录。请检查你的一行回答,对于初学者来说很容易选择它们。 - Titou
5
注意:os.listdir 列出目录中的内容,包括文件。 - guneysus

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