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

python exception path directory operating-system

如何检查要写入文件的目录是否存在,如果不存在,则使用 Python 创建目录?

通常,您可能需要考虑文件名中没有目录的情况。在我的机器上 dirname('foo.txt') 给出'',它不存在并导致 makedirs() 失败。

如果路径存在,则不仅要检查它是目录而不是常规文件还是其他对象(许多答案都检查这个),还需要检查它是否可写(我没有找到检查这个的答案)

如果您来这里是为了创建文件路径字符串 p 的父目录,这里是我的代码片段:os.makedirs(p[:p.rindex(os.path.sep)], exist_ok=True)

B
Boris Verkhovskiy

在 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 并检查嵌入的错误代码(请参阅 Is there a cross-platform way of getting information from Python’s 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

...并通过允许 a keyword argument to os.makedirs called exist_ok(在 3.2+ 中)。

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

竞争条件很好,但 stackoverflow.com/questions/273192/#273208 中的方法将掩盖创建目录的失败。不要因为投反对票而感到难过——你不喜欢这个答案。这就是投票的目的。

请记住 os.path.exists() 不是免费的。如果正常情况是目录会存在,那么不存在的情况应作为异常处理。换句话说,尝试打开并写入您的文件,捕获 OSError 异常,并根据 errno 执行您的 makedir() 并重新尝试或重新引发。这会造成代码重复,除非您将编写内容包装在本地方法中。

os.path.exists 还为文件返回 True。我已经发布了一个答案来解决这个问题。

正如此处其他答案的评论者所指出的,自 Python 3.2 以来,os.makedirs()exists_ok 参数可用于涵盖如何处理路径的先前存在。

如果路径分隔符被意外遗漏,os.mkdirs() 可能会创建意外文件夹,当前文件夹与预期不符,路径元素包含路径分隔符。如果您使用 os.mkdir(),这些错误将引发异常,提醒您它们的存在。

A
Asclepius

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 反向端口。不要安装名为 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)

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

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

据我所知,这个答案几乎涵盖了所有特殊情况。我计划将其包装在“if not os.path.isdir()”中,因为我希望该目录几乎每次都存在,并且我可以通过这种方式避免异常。

@CharlesL。如果您的原因是性能,则异常可能比检查的磁盘 IO 便宜。

@jpmc26 但 makedirs 在仅检查抛出 OSError 时会执行额外的 stat、umask、lstat。

这是错误的答案,因为它引入了潜在的 FS 种族条件。请参阅 Aaron Hall 的回答。

正如@sleepycal 所说,这与接受的答案有类似的竞争条件。如果在引发错误和检查 os.path.isdir 之间其他人删除了该文件夹,您将引发该文件夹存在的错误、过时和令人困惑的错误。

v
vallentin

使用 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

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

接受的答案实际上是危险的,因为它具有竞争条件。不过,它更简单,所以如果你不知道比赛条件,或者认为它不适用于你,那显然是你的首选。

仅当 exception.errno != errno.EEXIST 会无意中忽略路径存在但为非目录对象(例如文件)时的情况,才引发异常。如果路径是非目录对象,则理想情况下应该引发异常。

请注意,上面的代码等价于 os.makedirs(path,exist_ok=True)

@Navin exist_ok 参数是在 Python 3.2 中引入的。它在 Python 2.x 中不存在。我会将其纳入我的答案中。

@HeikkiToivonen 从技术上讲,如果另一个程序正在修改您的程序的目录和文件,那么您的整个程序就是一个巨大的竞争条件。在代码创建它之后和实际将文件放入其中之前,如何阻止另一个程序删除该目录?

h
hiro protagonist

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

P
Peter Mortensen

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

G
Georgy

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

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

使用 try/except,您将掩盖目录创建中的错误,如果目录不存在但由于某种原因您无法创建它

j
jesterjunk

试试 os.path.exists 函数

if not os.path.exists(dir):
    os.mkdir(dir)
R
Russia Must Remove Putin

关于这种情况的具体情况的见解

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

文件名 = "/my/directory/filename.txt" 目录 = os.path.dirname(文件名)

我们希望避免覆盖内置函数 dir。此外,filepathfullfilepath 可能是比 filename 更好的语义名称,所以这样写会更好:

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

你的最终目标是打开这个文件,你最初声明,写,但你基本上是这样接近这个目标(基于你的代码),它打开文件进行阅读:

如果不是 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)
P
Peter Mortensen

我已经把以下内容。不过,这也不是万无一失的。

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

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

R
Russia Must Remove Putin

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

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

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) 用户可调用函数,用于创建并返回唯一的临时目录。返回值是目录的路径名。该目录只能由创建用户读取、写入和搜索。调用者负责在完成后删除目录。

Python 3.5 中的新功能:pathlib.Path 和 exists_ok

有一个新的 Path 对象(从 3.4 开始),其中包含许多希望与路径一起使用的方法 - 其中之一是 mkdir

(对于上下文,我正在使用脚本跟踪我的每周代表。以下是脚本中代码的相关部分,可以让我避免每天针对相同的数据多次访问 Stack Overflow。)

首先是相关的进口:

from pathlib import Path
import tempfile

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

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

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

directory.mkdir(exist_ok=True)

以下是 documentation 的相关部分:

如果exist_ok 为真,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,然后其他需要 str 路径的 API 才能使用它们。

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

A
Antti Haapala -- Слава Україні

在 Python 3.4 中,您还可以使用 brand new pathlib module

from pathlib import Path
path = Path("/my/directory/filename.txt")
try:
    if not path.parent.exists():
        path.parent.mkdir(parents=True)
except OSError:
    # handle error; you can also catch specific errors like
    # FileExistsError and so on.
L
Lutz Prechelt

对于单行解决方案,您可以使用 IPython.utils.path.ensure_dir_exists()

from IPython.utils.path import ensure_dir_exists
ensure_dir_exists(dir)

documentation确保目录存在。如果它不存在,请尝试创建它并在另一个进程正在执行相同操作时防止出现竞争条件。

IPython 是一个扩展包,不是标准库的一部分。

C
Community

Python3 中,os.makedirs 支持设置 exist_ok。默认设置为 False,这意味着如果目标目录已经存在,则会引发 OSError。通过将 exist_ok 设置为 True,将忽略 OSError(目录存在)并且不会创建目录。

os.makedirs(path,exist_ok=True)

Python2 中,os.makedirs 不支持设置 exist_ok。您可以使用 heikki-toivonen's answer 中的方法:

import os
import errno

def make_sure_path_exists(path):
    try:
        os.makedirs(path)
    except OSError as exception:
        if exception.errno != errno.EEXIST:
            raise
P
Peter Mortensen

relevant Python documentation 建议使用 EAFP coding style (Easier to Ask for Forgiveness than Permission)。这意味着代码

try:
    os.makedirs(path)
except OSError as exception:
    if exception.errno != errno.EEXIST:
        raise
    else:
        print "\nBE CAREFUL! Directory %s already exists." % path

比替代品更好

if not os.path.exists(path):
    os.makedirs(path)
else:
    print "\nBE CAREFUL! Directory %s already exists." % path

文档表明这一点正是因为这个问题中讨论的竞争条件。此外,正如其他人在这里提到的那样,查询一次而不是两次操作系统具有性能优势。最后,在某些情况下可能支持第二个代码的论点——当开发人员知道应用程序正在运行的环境时——只能在程序设置了私有环境的特殊情况下被提倡。本身(以及同一程序的其他实例)。

即使在这种情况下,这也是一种不好的做法,并且可能导致长时间无用的调试。例如,我们为目录设置权限这一事实不应该给我们留下印象权限是为我们的目的而适当设置的。可以使用其他权限安装父目录。一般来说,一个程序应该总是正确地工作,程序员不应该期望一个特定的环境。

D
Devil

在 python 中执行此操作的最佳方法

#Devil
import os
directory = "./out_dir/subdir1/subdir2"
if not os.path.exists(directory):
    os.makedirs(directory)
V
Victoria Stuart

在我对在 Python 中使用目录时遇到的一些失败和错误感到困惑之后,我发现了这个 Q/A。我正在使用 Python 3(Arch Linux x86_64 系统上的 Anaconda 虚拟环境中的 v.3.5)。

考虑这个目录结构:

└── output/         ## dir
   ├── corpus       ## file
   ├── corpus2/     ## dir
   └── subdir/      ## dir

这是我的实验/笔记,提供了说明:

# ----------------------------------------------------------------------------
# [1] https://stackoverflow.com/questions/273192/how-can-i-create-a-directory-if-it-does-not-exist

import pathlib

""" Notes:
        1.  Include a trailing slash at the end of the directory path
            ("Method 1," below).
        2.  If a subdirectory in your intended path matches an existing file
            with same name, you will get the following error:
            "NotADirectoryError: [Errno 20] Not a directory:" ...
"""
# Uncomment and try each of these "out_dir" paths, singly:

# ----------------------------------------------------------------------------
# METHOD 1:
# Re-running does not overwrite existing directories and files; no errors.

# out_dir = 'output/corpus3'                ## no error but no dir created (missing tailing /)
# out_dir = 'output/corpus3/'               ## works
# out_dir = 'output/corpus3/doc1'           ## no error but no dir created (missing tailing /)
# out_dir = 'output/corpus3/doc1/'          ## works
# out_dir = 'output/corpus3/doc1/doc.txt'   ## no error but no file created (os.makedirs creates dir, not files!  ;-)
# out_dir = 'output/corpus2/tfidf/'         ## fails with "Errno 20" (existing file named "corpus2")
# out_dir = 'output/corpus3/tfidf/'         ## works
# out_dir = 'output/corpus3/a/b/c/d/'       ## works

# [2] https://docs.python.org/3/library/os.html#os.makedirs

# Uncomment these to run "Method 1":

#directory = os.path.dirname(out_dir)
#os.makedirs(directory, mode=0o777, exist_ok=True)

# ----------------------------------------------------------------------------
# METHOD 2:
# Re-running does not overwrite existing directories and files; no errors.

# out_dir = 'output/corpus3'                ## works
# out_dir = 'output/corpus3/'               ## works
# out_dir = 'output/corpus3/doc1'           ## works
# out_dir = 'output/corpus3/doc1/'          ## works
# out_dir = 'output/corpus3/doc1/doc.txt'   ## no error but creates a .../doc.txt./ dir
# out_dir = 'output/corpus2/tfidf/'         ## fails with "Errno 20" (existing file named "corpus2")
# out_dir = 'output/corpus3/tfidf/'         ## works
# out_dir = 'output/corpus3/a/b/c/d/'       ## works

# Uncomment these to run "Method 2":

#import os, errno
#try:
#       os.makedirs(out_dir)
#except OSError as e:
#       if e.errno != errno.EEXIST:
#               raise
# ----------------------------------------------------------------------------

结论:在我看来,“方法2”更健壮。

[1] How can I safely create a nested directory?

[2] https://docs.python.org/3/library/os.html#os.makedirs

您的方法 1 不起作用的原因是对 os.dirname 的调用。没有它,方法 1 将按预期工作。

S
Simone

最快最安全的方法是:如果不存在则创建,如果存在则跳过:

from pathlib import Path
Path("path/with/childs/.../").mkdir(parents=True, exist_ok=True)
D
Dennis Golomazov

您可以使用 mkpath

# Create a directory and any missing ancestor directories. 
# If the directory already exists, do nothing.

from distutils.dir_util import mkpath
mkpath("test")    

请注意,它也会创建祖先目录。

它适用于 Python 2 和 3。

J
Jean-François Fabre

如果在支持命令 mkdir-p 选项的机器上运行,为什么不使用子进程模块?适用于 python 2.7 和 python 3.6

from subprocess import call
call(['mkdir', '-p', 'path1/path2/path3'])

应该在大多数系统上做到这一点。

在可移植性无关紧要的情况下(例如,使用 docker),解决方案是干净的 2 行。您也不必添加逻辑来检查目录是否存在。最后,重新运行是安全的,没有任何副作用

如果您需要错误处理:

from subprocess import check_call
try:
    check_call(['mkdir', '-p', 'path1/path2/path3'])
except:
    handle...
H
Hussam Kurd

您必须在创建目录之前设置完整路径:

import os,sys,inspect
import pathlib

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
your_folder = currentdir + "/" + "your_folder"

if not os.path.exists(your_folder):
   pathlib.Path(your_folder).mkdir(parents=True, exist_ok=True)

这对我有用,希望它也对你有用

D
Dominykas Mostauskis

如果您将文件写入变量路径,您可以在文件路径上使用它来确保创建父目录。

from pathlib import Path

path_to_file = Path("zero/or/more/directories/file.ext")
parent_directory_of_file = path_to_file.parent
parent_directory_of_file.mkdir(parents=True, exist_ok=True)

即使 path_to_filefile.ext(零目录深度)也可以工作。

请参阅 pathlib.PurePath.parentpathlib.Path.mkdir

P
Peter Mortensen

我看到 Heikki ToivonenA-B-B 的答案并想到了这种变化。

import os
import errno

def make_sure_path_exists(path):
    try:
        os.makedirs(path)
    except OSError as exception:
        if exception.errno != errno.EEXIST or not os.path.isdir(path):
            raise
P
Peter Mortensen

我使用 os.path.exists()here 是一个 Python 3 脚本,可用于检查目录是否存在,如果不存在则创建一个,如果存在则删除它(如果需要)。

它提示用户输入目录并且可以很容易地修改。

J
Jean-François Fabre

使用此命令检查并创建目录

 if not os.path.isdir(test_img_dir):
     os.mkdir(test_img_dir)
S
Steffi Keran Rani J

在程序/项目的入口点调用函数 create_dir()

import os

def create_dir(directory):
    if not os.path.exists(directory):
        print('Creating Directory '+directory)
        os.makedirs(directory)

create_dir('Project directory')
R
Ralph Schwerdt

如果您考虑以下情况:

os.path.isdir('/tmp/dirname')

表示存在目录(路径)并且是目录。所以对我来说,这种方式可以满足我的需要。所以我可以确保它是文件夹(不是文件)并且存在。

d
dippas

您可以为此使用 os.listdir

import os
if 'dirName' in os.listdir('parentFolderPath')
    print('Directory Exists')
k
korakot

这可能不能完全回答这个问题。但我猜你的真正意图是创建一个文件及其父目录,因为它的内容全部在 1 个命令中。

您可以使用 pathlib 的 fastcore 扩展来做到这一点:path.mk_write(data)

from fastcore.utils import Path
Path('/dir/to/file.txt').mk_write('Hello World')

fastcore documentation 中查看更多信息

S
Sergiy Maksymenko

您可以使用系统调用创建嵌套目录 dir1/dir2/...。说在Linux下可以这样做:

import os
dirs='dir1/dir2/dir3'
os.system("mkdir -p {0}".format(dirs))

标志 -p 检查目录是否存在,在这种情况下不会生成任何错误消息。

让我提一下,这看起来不像是完全由 Python 方法完成的,任何做同样事情的 Python 库都应该在内部使用上述类型的系统调用。