如何安全地创建嵌套目录?

检查文件目录是否存在的最优雅方法是什么?如果不存在,则使用 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)

是否有 “打开” 标志,使它自动发生?

答案

pathlib.Path.mkdir ,使用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 (在FileExistsError版本中)都FileExistsError改善了此代码。

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

...,并允许os.makedirs的关键字参数称为exist_ok (在 3.2 + 中)。

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

Python 3.5 以上版本:

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

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

Python 3.2 以上版本:

使用pathlib

如果可以,请安装名为pathlib2的当前pathlib pathlib2 。不要安装名为pathlib的较旧的未维护的pathlib 。接下来,请参考上面的 Python 3.5 + 部分,并对其进行相同的使用。

如果使用 Python 3.4,即使pathlib附带了pathlib ,它也会缺少有用的exist_ok选项。反向端口旨在提供mkdir的更新和更高级的实现,其中包括此缺少的选项。

使用os

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

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

Python 2.7+:

使用pathlib

如果可以,请安装名为pathlib2的当前pathlib pathlib2 。不要安装名为pathlib的较旧的未维护的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创建嵌套目录,如果目录已经存在, mkpath执行任何操作。这适用于 Python 2 和 3。

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

根据Bug 10948 ,此替代方案的严重局限性在于,对于给定路径,每个 python 进程仅工作一次。换句话说,如果使用它来创建目录,然后从 Python 内部或外部删除目录,然后再次使用mkpath来重新创建同一目录,则mkpath会简单地静默使用其先前已创建目录的无效缓存信息,并且实际上不会再次创建目录。相反, os.makedirs不依赖任何此类缓存。对于某些应用程序,此限制可能是可以的。


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

使用 tryexcept 和来自 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引发OSError (权限被拒绝,错误 13)。

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

... 如果使用os.path.exists()测试,则将该参数传递给os.makedirs()时,将以一个名为filename.etc的目录结尾。

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

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

从 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) ))

对这种情况的具体见解

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

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)

如果目录或文件不存在,您将得到一个带有相关错误号的IOErrorerrno.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)

尝试os.path.exists函数

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

我将以下内容放下。但是,这并非完全安全。

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

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

检查目录是否存在并根据需要创建目录?

对此的直接答案是,假设有一个简单的情况,您不希望其他用户或进程弄乱您的目录:

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.Pathexist_ok

有一个新的Path对象(从 3.4 版本开始),它具有许多要与路径一起使用的方法 - 其中之一是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仅当最后一个路径组件不是现有的非目录文件时,才会忽略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))

在其他期望str路径可以使用它们的 API 之前,必须将Path对象强制转换为str

也许应该更新 Pandas 以接受抽象基类os.PathLike