我该如何创建一个目录,并创建任何缺失的父级目录?

5602
如何在给定的路径下创建目录,并创建该路径上任何缺失的父目录?例如,Bash 命令 mkdir -p /path/to/nested/directory 可以实现此功能。

41
一般情况下,你可能需要考虑文件名中不存在目录的情况。在我的机器上,dirname('foo.txt')返回'',表示该目录不存在,会导致makedirs()函数失败。请注意不改变原意,使翻译更加通俗易懂。 - Brian Hawkins
9
如果路径存在,不仅需要检查它是否是一个目录而不是一个普通文件或其他对象(许多答案都会检查这一点),还需要检查它是否可写(我没有找到检查这个的答案)。 - miracle173
14
如果您想创建文件路径字符串 p 的父目录,这是我的代码片段:os.makedirs(p[:p.rindex(os.path.sep)], exist_ok=True) - Thamme Gowda
在 Windows 10 中,如果我在文件浏览器中显示目录,则使用 Python(使用3.7.4版本)创建该目录,即使指定exist_ok=True也会返回“文件已存在”的错误。但是,当我关闭文件浏览器并重新运行Python程序时,程序可以正常运行。 - Bruce
29个回答

7007

在 Python ≥ 3.5 上,使用 pathlib.Path.mkdir

from pathlib import Path
Path("/my/directory").mkdir(parents=True, exist_ok=True)

针对较旧版本的Python,我看到两个质量较好但各有小缺陷的答案,因此我将给出我的看法:

尝试使用os.path.exists,并考虑使用os.makedirs进行创建。

import os
if not os.path.exists(directory):
    os.makedirs(directory)

正如注释和其他地方所述,存在一种竞争条件 - 如果目录在os.path.existsos.makedirs调用之间创建,则os.makedirs将失败,并出现一个OSError。不幸的是,对OSError进行全面捕获并继续执行不能百分之百保证成功,因为它会忽略由于其他因素(例如权限不足、磁盘已满等)导致的创建目录失败。

其中一个选择是捕获OSError并检查嵌入的错误代码(请参见有没有一种跨平台的方法可以从Python的OSError中获取信息?):

import os, errno

try:
    os.makedirs(directory)
except OSError as e:
    if e.errno != errno.EEXIST:
        raise

或者,还可以有第二个os.path.exists函数,但是假设另一个函数在第一次检查后创建了该目录,然后在第二次检查前将其删除 - 我们仍然会被欺骗。

根据应用程序的不同,同时进行操作的危险性可能高于或低于其他因素(如文件权限)带来的危险性。在选择实现之前,开发人员需要更多地了解正在开发的特定应用程序及其预期环境。

Python的现代版本通过展示FileExistsError(3.3+中)等方式有效改进了此代码...

try:
    os.makedirs("path/to/directory")
except FileExistsError:
    # directory already exists
    pass

... 并且通过在 3.2+ 版本中允许 一个名为 exist_ok 的关键字参数传递给 os.makedirs

os.makedirs("path/to/directory", exist_ok=True)  # succeeds even if directory exists.

12
竞争条件是一个好的观点,但是在https://dev59.com/L3VC5IYBdhLWcg3wfxU8中所采用的方法会掩盖创建目录失败的情况。不要因为投票反对而感到糟糕-你不喜欢这个答案。这就是投票的作用。 - Blair Conrad
38
请记住,os.path.exists()函数并非免费。如果通常情况下目录是存在的,那么目录不存在的情况应该被视为异常情况来处理。换句话说,尝试打开并写入文件,捕获OSError异常,根据errno执行makedir()并重试或重新抛出异常。这会导致代码重复,除非您将写入操作包装在一个本地方法中。 - Andrew
27
os.path.exists 也会对文件返回True。我已发布了一个回答来解决这个问题。 - Asclepius
18
正如其他答案的评论者所指出的那样,自Python 3.2以来,os.makedirs()exists_ok参数可用于覆盖如何处理路径的先前存在。 - Bobble
9
如果意外遗漏了路径分隔符、当前文件夹与预期不符,或者某个路径元素包含路径分隔符,os.mkdirs() 可能会创建出意想不到的文件夹。如果使用 os.mkdir(),这些问题将引发异常并提醒您它们的存在。请注意,涉及文件夹路径时要格外小心。 - drevicko
如果您想在当前工作目录中创建目录,请省略第一个斜杠 Path("my_directory").mkdir(parents=True, exist_ok=True) - do-me

1529

Python 3.5+:

import pathlib
pathlib.Path('/my/directory').mkdir(parents=True, exist_ok=True) 

pathlib.Path.mkdir 递归地创建目录,如果该目录已经存在,则不会引发异常。如果您不需要或希望创建父级目录,请跳过 parents 参数。

Python 3.2+:

使用 pathlib

如果可以的话,请安装名为 pathlib2 的当前 pathlib 后端口。不要安装老的未维护的后端口,名为 pathlib。接下来,请参考上面的 Python 3.5+ 部分并使用相同的方法。

如果使用的是 Python 3.4,即使它带有 pathlib,也缺少有用的 exist_ok 选项。该后端口旨在提供一个新的和更优秀的 mkdir 实现,其中包括这个缺失的选项。

使用 os

import os
os.makedirs(path, exist_ok=True)

os.makedirs 递归地创建目录,如果目录已存在,则不会引发异常。仅当使用 Python 3.2+ 时,它才具有可选参数 exist_ok,默认值为 False。在 Python 2.x 中直到 2.7,不存在此参数。因此,无需像使用 Python 2.7 那样进行手动异常处理。

Python 2.7+:

使用 pathlib

如果可能的话,请安装当前的名为 pathlib2 的回溯。不要安装旧的未维护的名为 pathlib 的回溯。接下来,参考上面的 Python 3.5+ 部分并以相同方式使用。

使用 os

import os
try: 
    os.makedirs(path)
except OSError:
    if not os.path.isdir(path):
        raise
虽然一个天真的解决方案可能会首先使用os.path.isdir,然后再使用os.makedirs,但上述解决方案颠倒了两个操作的顺序。通过这样做,它可以防止与重复创建目录有关的常见竞争条件,并消除文件和目录之间的歧义。
请注意,捕获异常并使用errno的用处有限,因为OSError: [Errno 17] File exists(即errno.EEXIST)对文件和目录都会引发。更可靠的方法是简单地检查目录是否存在。
替代方案:使用mkpath创建嵌套目录,如果目录已经存在,则不执行任何操作。这适用于Python 2和3。但是请注意,distutils已被弃用,并计划在Python 3.12中删除。
import distutils.dir_util
distutils.dir_util.mkpath(path)
根据Bug 10948,这种替代方式的一个严重限制是,对于给定路径,在python进程中仅能使用一次。换句话说,如果您使用它创建一个目录,然后从Python内部或外部删除该目录,然后再次使用mkpath重新创建相同的目录,则mkpath将简单地静默地使用其无效的缓存信息,表示先前已经创建了该目录,并且不会实际再次创建该目录。相比之下,os.makedirs不依赖任何此类缓存。这种限制可能对某些应用程序没关系。

关于目录的模式,如果您关心它,请参阅文档。

17
据我所知,这个答案基本涵盖了所有特殊情况。我打算加上一个“if not os.path.isdir()”的语句,因为我几乎每次都期望目录存在,通过这种方式可以避免异常。 - Charles L.
7
如果你的理由是性能,那么异常处理可能比检查的磁盘IO更便宜。 - jpmc26
2
@jpmc26 但是makedirs在仅检查以抛出OSError时会执行额外的stat、umask和lstat操作。 - kwarunek
6
这个答案是错误的,因为它引入了可能的文件系统竞争条件。请查看Aaron Hall的答案。 - SleepyCal
6
正如@sleepycal所说,这个问题存在与被接受的答案类似的竞态条件。如果在引发错误和检查 os.path.isdir 之间有其他人删除了该文件夹,则会引发错误,提示该文件夹存在,但实际上是错误的、过时的且容易引起混淆的错误信息。 - farmir
显示剩余5条评论

657

使用try except和errno模块中正确的错误代码可以消除竞态条件且具有跨平台性:

import os
import errno

def make_sure_path_exists(path):
    try:
        os.makedirs(path)
    except OSError as exception:
        if exception.errno != errno.EEXIST:
            raise
换句话说,我们尝试创建目录,但如果它们已经存在,我们会忽略错误。另一方面,任何其他错误都会报告。例如,如果您事先创建了目录'a'并从中删除了所有权限,则将引发一个具有errno.EACCES(权限被拒绝,错误13)的OSError

26
采纳的答案实际上存在竞态条件,这是危险的。不过,如果你不知道竞态条件的存在,或者认为它对你没有影响,那么这个答案可能更简单明了,也是你明显的首选。请注意,简单并不一定安全。 - Heikki Toivonen
18
仅在 exception.errno != errno.EEXIST 时引发异常将无意中忽略路径存在但是是非目录对象(如文件)的情况。如果路径是非目录对象,则理想情况下应该引发异常。 - Asclepius
194
请注意,上述代码等同于 os.makedirs(path,exist_ok=True) - Navin
60
@Navin,“exist_ok”参数是在Python 3.2中引入的,而在Python 2.x中不存在。我将把它纳入我的答案中。 - Asclepius
28
从技术上讲,如果另一个程序在您的程序运行时同时修改目录和文件,那么您的整个程序就是一个巨大的竞态条件。如果代码创建目录后,在您实际放入文件之前,有其他程序删除该目录,那么会发生什么呢? - jpmc26
显示剩余8条评论

138

从Python 3.5开始,pathlib.Path.mkdir拥有一个exist_ok标志:

from pathlib import Path
path = Path('/my/directory/filename.txt')
path.parent.mkdir(parents=True, exist_ok=True) 
# path.parent ~ os.path.dirname(path)

这将递归地创建目录,并且如果目录已经存在,不会引发异常。

(与 os.makedirs 一样,从 Python 3.2 开始,它也有一个exist_ok标志,例如:os.makedirs(path, exist_ok=True))


注意:当我发布这个答案时,其他答案都没有提到 exist_ok ...


请注意:如果文件夹名称和文件名在同一位置相同,则即使存在exist_ok标志,也会出现“[Errno 17] File exists”错误。 - lam vu Nguyen

130
我个人建议您使用os.path.isdir()进行测试,而不是使用os.path.exists()
>>> os.path.exists('/tmp/dirname')
True
>>> os.path.exists('/tmp/dirname/filename.etc')
True
>>> os.path.isdir('/tmp/dirname/filename.etc')
False
>>> os.path.isdir('/tmp/fakedirname')
False

如果您有以下内容:
>>> directory = raw_input(":: ")

还有一个愚蠢的用户输入:

:: /tmp/dirname/filename.etc

如果你将filename.etc这个参数传递给os.makedirs()函数,并使用os.path.exists()函数做测试,最终你会得到一个名为filename.etc的目录。


110

检查 os.makedirs: (它确保完整路径存在。)
为了处理目录可能已经存在的情况,请捕获 OSError。(如果 exist_okFalse(默认),则会引发一个 OSError 如果目标目录已经存在。)

import os
try:
    os.makedirs('./path/to/somewhere')
except OSError:
    pass

25
通过使用try/except,您可以掩盖目录创建中的错误,在目录不存在但由于某种原因无法创建时。 - Blair Conrad

84

尝试使用os.path.exists函数。

if not os.path.exists(dir):
    os.mkdir(dir)

1
这不会创建父目录。根据os.mkdir文档:“如果路径中的父目录不存在,则会引发FileNotFoundError。” - Ryan M

62

关于这种情况的见解

你提供了一个特定路径下的文件,并从文件路径中获取目录。然后,在确保你拥有该目录后,尝试打开一个文件进行读取。对于这段代码的评论:

filename = "/my/directory/filename.txt"
dir = os.path.dirname(filename)

我们希望避免覆盖内置函数dir。此外,filepath或者也许fullfilepath是比filename更好的语义名称,因此最好这样编写:

import os
filepath = '/my/directory/filename.txt'
directory = os.path.dirname(filepath)

你的最终目标是打开这个文件进行写入,但是根据你的代码,你实际上是以打开文件进行读取的方式来实现这个目标的:
if not os.path.exists(directory):
    os.makedirs(directory)
f = file(filename)

假设打开以进行读取

如果您期望文件存在并能够读取,为什么要为其创建目录?

只需尝试打开该文件即可。

with open(filepath) as my_file:
    do_stuff(my_file)

如果目录或文件不存在,你会得到一个 IOError ,并附有一个错误号: 无论你的平台如何,errno.ENOENT 都会指向正确的错误号。例如,你可以捕获它:

import errno
try:
    with open(filepath) as my_file:
        do_stuff(my_file)
except IOError as error:
    if error.errno == errno.ENOENT:
        print 'ignoring error because directory or file is not there'
    else:
        raise

假设我们要打开进行写入

这很可能是你想要的。

在这种情况下,我们可能不会遇到任何竞争条件。所以按照你原来的方式操作即可,但请注意,对于写入,您需要使用w模式(或a进行追加)进行打开。在Python中,使用上下文管理器打开文件也是最佳实践。

import os
if not os.path.exists(directory):
    os.makedirs(directory)
with open(filepath, 'w') as my_file:
    do_stuff(my_file)

然而,假设我们有几个Python进程试图将它们的所有数据放入同一个目录中。那么我们可能会在创建目录时发生争用。在这种情况下,最好将makedirs调用包装在try-except块中。

import os
import errno
if not os.path.exists(directory):
    try:
        os.makedirs(directory)
    except OSError as error:
        if error.errno != errno.EEXIST:
            raise
with open(filepath, 'w') as my_file:
    do_stuff(my_file)

44

我已经写下了以下内容。但这并非完全可靠。

import os

dirname = 'create/me'

try:
    os.makedirs(dirname)
except OSError:
    if os.path.exists(dirname):
        # We are nearly safe
        pass
    else:
        # There was an error on creation, so make sure we know about it
        raise

正如我所说的,这并不是绝对可靠的,因为我们有可能在创建目录时失败,并且另一个进程在此期间创建了该目录。


35

检查目录是否存在并在需要时创建它?

在假设您不希望其他用户或进程干扰您的目录的简单情况下,直接回答是:

if not os.path.exists(d):
    os.makedirs(d)

或者如果创建目录会受到竞态条件的影响(即在检查路径是否存在之后,可能已经有其他东西创建了它),请执行以下操作:

import errno
try:
    os.makedirs(d)
except OSError as exception:
    if exception.errno != errno.EEXIST:
        raise

但也许一种更好的方法是通过tempfile使用临时目录来避免资源竞争问题:

import tempfile

d = tempfile.mkdtemp()

以下是在线文档中的要点:

mkdtemp(suffix='', prefix='tmp', dir=None)
    User-callable function to create and return a unique temporary
    directory.  The return value is the pathname of the directory.

    The directory is readable, writable, and searchable only by the
    creating user.

    Caller is responsible for deleting the directory when done with it.

Python 3.5新特性: pathlib.Path 带有 exist_ok

Python 3.4引入了一个新的Path对象,提供许多有关路径操作的方法,其中之一是mkdir

(为了背景,我正在使用脚本跟踪我的每周声望值。下面是脚本中相关的代码部分,它使我避免因相同数据每天只能在Stack Overflow上查询一次。)

首先是相关的导入:

from pathlib import Path
import tempfile

现在我们不需要处理 os.path.join - 只需用 / 来连接路径部分:

directory = Path(tempfile.gettempdir()) / 'sodata'

然后我采用幂等性确保目录存在 - 在Python 3.5中出现了exist_ok参数:

directory.mkdir(exist_ok=True)

这是文档中相关部分:

如果 exist_ok 为 true,则忽略 FileExistsError 异常(与 POSIX 中的 mkdir -p 命令相同),但仅当最后一部分路径不是现有的非目录文件时。

这是脚本的更多内容——在我的情况下,我不会受到竞态条件的影响,我只有一个进程期望该目录(或包含的文件)存在,并且我没有任何尝试删除该目录的东西。

todays_file = directory / str(datetime.datetime.utcnow().date())
if todays_file.exists():
    logger.info("todays_file exists: " + str(todays_file))
    df = pd.read_json(str(todays_file))

Path 对象必须在其它期望 str 路径的 API 使用前转换为 str

或许 Pandas 应该更新,接受抽象基类 os.PathLike 的实例。


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