题 如何在Python中安全地创建嵌套目录?


检查文件目录是否存在的最优雅方法是什么,如果不存在,使用Python创建目录?这是我尝试过的:

import os

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

try:
    os.stat(directory)
except:
    os.mkdir(directory)       

f = file(filename)

不知怎的,我错过了 os.path.exists (感谢kanja,Blair和Douglas)。这就是我现在拥有的:

def ensure_dir(file_path):
    directory = os.path.dirname(file_path)
    if not os.path.exists(directory):
        os.makedirs(directory)

是否有“开放”的标志,这会自动发生?


2989
2017-11-07 18:56


起源


通常,您可能需要考虑文件名中没有目录的情况。在我的机器上,dirname('foo.txt')给出'',它不存在并导致makedirs()失败。 - Brian Hawkins
在python 2.7中 os.path.mkdir 不存在。它的 os.mkdir。 - drevicko
如果路径存在,不仅要检查它是否是目录而不是常规文件或其他对象(许多答案检查这个),还有必要检查它是否可写(我没有找到检查这个的答案) - miracle173
如果你来这里创建文件路径字符串的父目录 p,这是我的代码片段: os.makedirs(p[:p.rindex(os.path.sep)], exist_ok=True) - Thamme Gowda
在这个问题的答案的元讨论 - gnat


答案:


我看到两个具有良好品质的答案,每个答案都有一个小缺陷,所以我会考虑它:

尝试 os.path.exists,并考虑 os.makedirs 为了创造。

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

正如评论和其他地方所述,存在竞争条件 - 如果目录是在。之间创建的 os.path.exists 和 os.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,但假设另一个人在第一次检查后创建了目录,然后在第二次检查之前将其删除 - 我们仍然可以被愚弄。

根据应用程序,并发操作的危险可能多于或少于文件权限等其他因素造成的危险。在选择实现之前,开发人员必须更多地了解正在开发的特定应用程序及其预期环境。


3691
2017-11-07 19:06



同意try / except解决方案更好 - Corey Goldberg
请记住,os.path.exists()不是免费的。如果正常情况是目录将在那里,那么不应将其作为例外处理。换句话说,尝试打开并写入您的文件,捕获OSError异常,并根据errno,执行您的makedir()并重新尝试或重新加注。这会创建代码重复,除非您在本地方法中包装写入。 - Andrew
os.path.exists 也回来了 True 对于一个文件。我已经发布了一个答案来解决这个问题。 - A-B-B
os.mkdirs() 如果意外地遗漏了路径分隔符,则可以创建非预期的文件夹,当前文件夹不是预期的,路径元素包含路径分隔符。如果你使用 os.mkdir() 这些错误会引发异常,提醒您它们的存在。 - drevicko
所以实际答案应该是 stackoverflow.com/questions/273192/... - user3885927


Python 3.5+:

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

pathlib.Path.mkdir 如上所用,递归创建目录,如果目录已存在则不引发异常。如果您不需要或希望创建父项,请跳过 parents 论据。

Python 3.2+:

运用 pathlib

如果可以,请安装当前的 pathlib 后端命名 pathlib2。不要安装名为的旧的非维护后端口 pathlib。接下来,请参阅上面的Python 3.5+部分并使用它。

如果使用Python 3.4,即使它附带 pathlib,它缺少有用的 exist_ok 选项。 backport旨在提供更新更好的实现 mkdir 其中包括这个缺失的选项。

运用 os

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

os.makedirs 如上所用,递归创建目录,如果目录已存在则不引发异常。它有可选的 exist_ok 参数仅在使用Python 3.2+时,默认值为 False。这个参数在Python 2.x中不存在,最高可达2.7。因此,不需要像Python 2.7那样进行手动异常处理。

Python 2.7+:

运用 pathlib

如果可以,请安装当前的 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。

import distutils.dir_util
distutils.dir_util.mkpath(path)

错误10948,这个替代方案的一个严重限制是它对于给定路径每个python进程只能工作一次。换句话说,如果您使用它来创建目录,那么从Python内部或外部删除目录,然后使用 mkpath 再次重新创建相同的目录, mkpath 将只是默默地使用其先前创建目录的无效缓存信息,并且实际上不会再次创建目录。相反, os.makedirs 不依赖于任何此类缓存。对于某些应用,此限制可能没问题。


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


815
2018-01-16 17:31



就我所知,这个答案涵盖了几乎所有特殊情况。我计划将其包装在“if not os.path.isdir()”中,但因为我希望目录几乎每次都存在,我可以避免这种异常。 - Charles L.
@CharlesL。如果您的原因是性能,则异常可能比检查的磁盘IO便宜。 - jpmc26
@jpmc26但是当仅检查抛出OSError时,makedirs会执行额外的stat,umask,lstat。 - kwarunek
这是错误的答案,因为它引入了潜在的FS竞赛cond。查看Aaron Hall的回答。 - sleepycal
正如@sleepycal所说,这种情况与公认的答案相似。如果在提高错误和检查之间 os.path.isdir 其他人删除该文件夹,您将引发该文件夹存在的错误,过时和混淆错误。 - farmir


使用try除了和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

换句话说,我们尝试创建目录,但如果它们已经存在,我们会忽略错误。另一方面,报告任何其他错误。例如,如果您事先创建了dir'a'并从中删除了所有权限,那么您将得到一个 OSError 提出来 errno.EACCES (许可被拒绝,错误13)。


573
2018-02-17 17:17



接受的答案实际上是危险的,因为它有竞争条件。但是,它更简单,所以如果你不知道竞争条件,或者认为它不适用于你,那将是你明显的首选。 - Heikki Toivonen
仅在提升异常时才提高异常 exception.errno != errno.EEXIST 当路径存在但是非目录对象(如文件)时,会无意中忽略这种情况。如果路径是非目录对象,则理想情况下应该引发异常。 - A-B-B
请注意,上面的代码相当于 os.makedirs(path,exist_ok=True) - Navin
@Navin The exist_ok 参数是在Python 3.2中引入的。它在Python 2.x中不存在。我会把它纳入我的答案。 - A-B-B
@HeikkiToivonen从技术上讲,如果另一个程序在你的程序同时修改目录和文件,你的整个程序就是一个巨大的竞争条件。什么是阻止另一个程序在代码创建之后以及在实际放入文件之前删除此目录? - jpmc26


我个人建议你使用 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

如果你有:

>>> dir = raw_input(":: ")

一个愚蠢的用户输入:

:: /tmp/dirname/filename.etc

...你最终会得到一个名为的目录 filename.etc 当你传递那个参数时 os.makedirs() 如果你测试 os.path.exists()


85
2018-01-14 17:57



如果仅使用“isdir”,当您尝试创建目录并且已存在同名文件时,是否仍然存在问题? - MrWonderful
@MrWonderful在现有文件上创建目录时产生的异常会将问题正确反映回调用者。 - Damian Yerrick
这是一个糟糕的建议。看到 stackoverflow.com/a/5032238/763269。 - Chris Johnson


检查 os.makedirs:(确保存在完整路径。)
 要处理目录可能存在的事实,请捕获OSError。 (如果exist_ok为False(默认值),则在目标目录已存在的情况下引发OSError。)

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

56
2017-11-07 19:01



使用try / except,你将掩盖目录创建中的错误,如果目录不存在但由于某种原因你无法成功 - Blair Conrad
这是唯一安全的方式。 - Ali Afshar
OSError 如果路径是现有文件或目录,将在此处引发。我已经发布了一个答案来解决这个问题。 - A-B-B
这是中途。您需要检查子错误情况 OSError 在决定忽略它之前。看到 stackoverflow.com/a/5032238/763269。 - Chris Johnson


洞察这种情况的具体情况

您在特定路径中提供特定文件,然后从文件路径中提取目录。然后在确保您拥有该目录后,尝试打开文件进行读取。要评论此代码:

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)

29
2018-01-22 23:49





从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 得到了 exists_ok 标志从python 3.2)开始。


28
2017-12-14 16:06





我已经放下了以下内容。但这并非完全万无一失。

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

现在正如我所说,这并非万无一失,因为我们有可能无法创建目录,而另一个进程在此期间创建它。


22
2017-11-07 21:23



stackoverflow.com/questions/273698/... - Ali Afshar
两个问题:(1)在决定检查之前,需要检查OSError的子错误情况 os.path.exists  - 请参阅stackoverflow.com/a/5032238/763269,以及(2)成功 os.path.exists 并不意味着目录存在,只是路径存在 - 可以是文件,符号链接或其他文件系统对象。 - Chris Johnson


试试吧 os.path.exists 功能

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

22
2017-11-07 19:00



我打算对这个问题发表评论,但我们的意思是os.mkdir吗?我的python(2.5.2)没有os.path.mkdir .... - Blair Conrad
没有 os.path.mkdir() 方法。 os.path中 模块实现一些有用的 路径名上的函数。 - Serge S.
这是一个糟糕的建议。看到 stackoverflow.com/a/5032238/763269。 - Chris Johnson