如何在Python中获取所有直接子目录

213
我试图编写一个简单的 Python 脚本,将 index.tpl 复制到所有子目录中的 index.html(除了一些例外情况)。
我一直在尝试获取子目录列表而感到困扰。

11
您可以在此前的SO问题中找到被接受的答案以解决问题:https://dev59.com/0nVD5IYBdhLWcg3wAWkO - Jarret Hardie
16个回答

251
import os
def get_immediate_subdirectories(a_dir):
    return [name for name in os.listdir(a_dir)
            if os.path.isdir(os.path.join(a_dir, name))]

160

我对返回所有当前子目录全路径的各种函数进行了一些速度测试.

总之,始终使用scandir:

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

额外加赠:使用scandir您还可以仅通过使用f.name而不是f.path来获取文件夹名称。

这(以及下面的所有其他功能)都不使用natural sorting。这意味着结果将按如下方式排序:1、10、2。 要获得自然排序(1、2、10),请查看https://dev59.com/Gm445IYBdhLWcg3wia2V#48030307




结果scandirwalk快3倍,比listdir(带过滤器)快32倍,比Pathlib快35倍,比listdir快36倍,比glob更快37倍。

Scandir:           0.977
Walk:              3.011
Listdir (filter): 31.288
Pathlib:          34.075
Listdir:          35.501
Glob:             36.277

在使用W7x64,Python 3.8.1进行测试时,有一个包含440个子文件夹的文件夹。
如果您想知道是否可以通过不进行两次os.path.join()来加速listdir,答案是肯定的,但实际上差别几乎无法察觉。

代码:

import os
import pathlib
import timeit
import glob

path = r"<example_path>"



def a():
    list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]
    # print(len(list_subfolders_with_paths))


def b():
    list_subfolders_with_paths = [os.path.join(path, f) for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))]
    # print(len(list_subfolders_with_paths))


def c():
    list_subfolders_with_paths = []
    for root, dirs, files in os.walk(path):
        for dir in dirs:
            list_subfolders_with_paths.append( os.path.join(root, dir) )
        break
    # print(len(list_subfolders_with_paths))


def d():
    list_subfolders_with_paths = glob.glob(path + '/*/')
    # print(len(list_subfolders_with_paths))


def e():
    list_subfolders_with_paths = list(filter(os.path.isdir, [os.path.join(path, f) for f in os.listdir(path)]))
    # print(len(list(list_subfolders_with_paths)))


def f():
    p = pathlib.Path(path)
    list_subfolders_with_paths = [x for x in p.iterdir() if x.is_dir()]
    # print(len(list_subfolders_with_paths))



print(f"Scandir:          {timeit.timeit(a, number=1000):.3f}")
print(f"Listdir:          {timeit.timeit(b, number=1000):.3f}")
print(f"Walk:             {timeit.timeit(c, number=1000):.3f}")
print(f"Glob:             {timeit.timeit(d, number=1000):.3f}")
print(f"Listdir (filter): {timeit.timeit(e, number=1000):.3f}")
print(f"Pathlib:          {timeit.timeit(f, number=1000):.3f}")

79

为什么没有人提到 globglob 允许您使用类 Unix 的路径名扩展,几乎可以用于查找多个路径名的所有操作。这使得它非常容易:

from glob import glob
paths = glob('*/')

注意,glob 会返回带有最终斜杠的目录(如 Unix),而大多数基于 path 的解决方案将省略最终斜杠。


3
好的解决方案,简单有效。对于那些不想要最后一个斜杠的人,他们可以使用这个 paths = [ p.replace('/', '') for p in glob('*/') ] - Evan Hu
5
[p[:-1] for p in paths] 可能更安全,因为 replace 方法也会替换文件名中任何转义的正斜杠(虽然这种情况很少见)。 - ari
3
更安全的做法是使用strip('/')函数去掉末尾的斜杠。这样可以确保不会误切除非正斜杠的其他字符。 - Eliezer Miron
8
施工时保证有尾随斜杠(因此不更安全),但我认为这样更易读。你肯定想使用 rstrip 而不是 strip,因为后者会将任何完全限定的路径转换为相对路径。 - ari
7
补充@ari的评论,特别是对于像我这样的Python新手:strip('/')会同时移除开头和结尾的斜杠,而rstrip('/')只会移除结尾的斜杠。 - Titou
显示剩余3条评论

44

2
非常聪明。虽然效率并不重要(...但实际上很重要),但我很好奇这个方法和基于glob的生成器表达式(s.rstrip("/") for s in glob(parent_dir+"*/"))哪个更加高效。我的直觉是基于stat()os.walk()解决方案应该比shell风格的globbing快得多。可惜,我没有意愿去使用timeit来真正找出答案。 - Cecil Curry
3
请注意,此返回子目录名称,而不包括其前缀为父目录名称的情况。 - Paul Chernoch
正是我所寻找的……只需要目录名称而不是路径- @Cecil 我尝试了您的方法,但无法使其工作。我尝试使用 tmplist=(s.rstrip("/") for s in glob(tmp+"*/")),其中tmp是我的父目录。它返回 <generator object <genexpr> at 0x0000018A88EE3660>。我做错了什么? - jrive

21
import os

获取目录中所有(完整路径的)直接子目录:

def SubDirPath (d):
    return filter(os.path.isdir, [os.path.join(d,f) for f in os.listdir(d)])

获取最新的子目录:

def LatestDirectory (d):
    return max(SubDirPath(d), key=os.path.getmtime)

要获取一个列表,只需添加 list( filter(...) ) - user136036

13

os.walk 是在这种情况下的好帮手。

引用自官方文档:

walk() 方法通过向上或向下遍历目录树,在目录树中生成文件名。对于目录树中的每个目录(包括顶级目录 top 本身),它会产生一个 3 元组(dirpath,dirnames,filenames)。


1
请注意,如果您只想获取第一级子目录,则在第一组返回值后退出 os.walk 迭代。 - yoyo

12

这种方法很好地一次性完成所有工作。

from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]

7
使用 Twisted 的 FilePath 模块:
from twisted.python.filepath import FilePath

def subdirs(pathObj):
    for subpath in pathObj.walk():
        if subpath.isdir():
            yield subpath

if __name__ == '__main__':
    for subdir in subdirs(FilePath(".")):
        print "Subdirectory:", subdir

有些评论者问了使用Twisted库的优点,我将在这里稍微超出原始问题。


这个分支中有一些改进的文档解释了FilePath的优点;你可能想阅读一下。

更具体地说,在这个例子中:与标准库版本不同,此函数可以实现无导入。 "subdirs"函数是完全通用的,因为它仅操作其参数。为了使用标准库复制和移动文件,您需要依赖于"open"内置,"listdir",也许"isdir"或"os.walk"或"shutil.copy"。也许还有"os.path.join"。更不用说您需要传递一个字符串作为参数来标识实际文件。让我们看一下完整的实现,它将把每个目录的“index.tpl”复制到“index.html”:

def copyTemplates(topdir):
    for subdir in subdirs(topdir):
        tpl = subdir.child("index.tpl")
        if tpl.exists():
            tpl.copyTo(subdir.child("index.html"))

上面的“subdirs”函数适用于任何类似于FilePath的对象。这意味着,ZipPath对象等等也可以使用。不幸的是,ZipPath目前是只读的,但它可以扩展以支持写入。
您还可以传递自己的对象进行测试。为了测试此处建议使用的使用os.path的API,您必须操纵导入的名称和隐式依赖关系,并通常执行黑魔法来使测试正常工作。使用FilePath,您可以像这样做:
class MyFakePath:
    def child(self, name):
        "Return an appropriate child object"

    def walk(self):
        "Return an iterable of MyFakePath objects"

    def exists(self):
        "Return true or false, as appropriate to the test"

    def isdir(self):
        "Return true or false, as appropriate to the test"
...
subdirs(MyFakePath(...))

由于我对 Twisted 的了解很少,因此我始终欢迎额外的信息和示例;这个答案对此非常好。话虽如此,既然这种方法似乎需要比使用内置的 Python 模块和 Twisted 安装更多的工作量,那么在使用此方法方面是否有任何优势可以添加到答案中呢? - Jarret Hardie
1
Glyph的答案可能是由于TwistedLore也使用.tpl文件所激发的灵感。 - Constantin
好的,显然我不希望进行西班牙审判 :-)我假设“*.tpl”是对某种抽象扩展名“template”的通用参考,而不是特定于Twisted的模板(毕竟我在许多语言中都看到过.tpl的使用)。 得知这一点很好。 - Jarret Hardie
因此,对于可能的Twisted角度进行调整,+1,尽管我仍然希望了解Twisted的“FilePath”对象和“walk()”函数对标准API的增强。 - Jarret Hardie
个人认为,“FilePath.walk() yields path objects”比“os.walk yields 3-tuples of dir, dirs, files”容易记忆得多。但是还有其他好处。FilePath允许多态性,这意味着您可以遍历除文件系统以外的其他内容。例如,您可以将twisted.python.zippath.ZipArchive传递给我的“subdirs”函数,并获得ZipPaths生成器而不是FilePaths;您的逻辑不会改变,但是您的应用程序现在神奇地处理zip文件。如果您想测试它,只需提供一个对象,而不必编写真实的文件。 - Glyph

4

我刚编写了一些代码来移动VMware虚拟机,并最终使用os.pathshutil来实现子目录之间的文件复制。

def copy_client_files (file_src, file_dst):
    for file in os.listdir(file_src):
            print "Copying file: %s" % file
            shutil.copy(os.path.join(file_src, file), os.path.join(file_dst, file))

这并不是非常优雅,但它确实有效。


2
def get_folders_in_directories_recursively(directory, index=0):
    folder_list = list()
    parent_directory = directory

    for path, subdirs, _ in os.walk(directory):
        if not index:
            for sdirs in subdirs:
                folder_path = "{}/{}".format(path, sdirs)
                folder_list.append(folder_path)
        elif path[len(parent_directory):].count('/') + 1 == index:
            for sdirs in subdirs:
                folder_path = "{}/{}".format(path, sdirs)
                folder_list.append(folder_path)

    return folder_list

以下函数可以这样调用:

get_folders_in_directories_recursively(directory, index=1) -> 返回第一级目录中的文件夹列表

get_folders_in_directories_recursively(directory) -> 返回所有子文件夹列表


做得很好,使用的是Python 3.6版本,但我需要从函数变量中删除"self"。 - locometro

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