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


4240

检查文件目录是否存在的最优雅方法是什么,如果不存在,则使用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(感谢魔芋,布莱尔和道格拉斯)。这就是我现在所拥有的:

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

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


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

11
在python 2.7 os.path.mkdir中不存在。是os.mkdir
drevicko

6
如果该路径存在,则不仅要检查它是否是目录,而不是常规文件或其他对象(许多答案检查了此),还需要检查它是否可写(我没有找到检查该问题的答案)
miracle173

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

Answers:


5182

在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

...并允许关键字参数os.makedirs调用exist_ok(在3.2+版本中)。

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

5
竞争条件是一个不错的选择,但是stackoverflow.com/questions/273192/#273208中的方法将掩盖创建目录的失败。不要为否决感到难过-您不喜欢答案。这是投票的目的。
布莱尔·康拉德

27
请记住,os.path.exists()不是免费的。如果通常情况是该目录将存在,则不存在该目录的情况应作为例外处理。换句话说,尝试打开并写入文件,捕获OSError异常,然后基于errno,执行makedir()并重试或重新引发。除非您将编写内容包装在本地方法中,否则这将导致代码重复。
安德鲁

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

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

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

1240

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选项。反向端口旨在提供更新的高级实现,mkdir其中包括缺少的选项。

使用os

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

os.makedirs上面使用的递归方式创建目录,并且如果目录已经存在,则不会引发异常。exist_ok仅当使用Python 3.2+时,它才具有可选参数,默认值为False。在2.7之前的Python 2.x中不存在此参数。这样,就不需要像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)

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


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


13
据我所知,这个答案几乎涵盖了所有特殊情况。我计划将其包装在“如果不是os.path.isdir()”中,因为我希望目录几乎每次都存在,因此可以避免这种异常。
Charles L.

5
@CharlesL。如果您的原因是性能,那么异常可能比检查的磁盘IO便宜。
jpmc26 2014年

1
@ jpmc26,但是当仅检查抛出OSError时,makedirs会执行其他stat,umask,lstat。
kwarunek 2014年

4
这是错误的答案,因为它引入了潜在的FS竞赛条件。请参阅亚伦·霍尔的答案。
sleepycal

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

604

使用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'并从中删除所有权限,则会OSError引发errno.EACCES(权限被拒绝,错误13)。


24
接受的答案实际上很危险,因为它具有竞争条件。但是,它更简单,因此,如果您不知道竞争条件,或者认为它不适用于您,那将是您显而易见的首选。
Heikki Toivonen'5

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

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

58
@Navin该exist_ok参数在Python 3.2中引入。它在Python 2.x中不存在。我会将其合并到我的答案中。
Acumenus

26
@HeikkiToivonen从技术上讲,如果另一个程序正在同时修改目录和文件,则整个程序是一个巨大的竞争条件。是什么要阻止另一个程序在代码创建它之后并且实际将文件放入其中之前删除该目录?
jpmc26 2014年

102

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


8
如果仅使用“ isdir”,当您尝试创建目录并且已经存在具有相同名称的文件时,是否还会有问题?
MrWonderful 2014年

3
@MrWonderful在现有文件上创建目录时出现的异常将正确地将问题反映给调用方。
Damian Yerrick

79

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

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

19
使用try / except,在目录不存在但由于某种原因而无法创建目录的情况下,您将掩盖目录创建中的错误
Blair Conrad

3
OSError如果路径是现有文件或目录,将在此处引发。我已经发布了一个答案来解决这个问题。
Acumenus

4
这是一半。您确实需要先检查的子错误条件,OSError然后再决定忽略它。请参阅stackoverflow.com/a/5032238/763269
克里斯·约翰逊

71

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

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

(就像从python 3.2开始os.makedirsexist_ok标志一样os.makedirs(path, exist_ok=True)


46

对这种情况的具体见解

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

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)

34

试用os.path.exists功能

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

3
我打算评论这个问题,但是我们是指os.mkdir吗?我的Python(2.5.2)没有os.path.mkdir ....
布莱尔康拉德

1
没有os.path.mkdir()办法。os.path模块在路径名上实现了一些有用的功能
Serge S.

31

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

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

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



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

24

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

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

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'

然后,我确定地确保目录存在- exist_ok参数在Python 3.5中显示:

directory.mkdir(exist_ok=True)

这是文档的相关部分:

如果exist_ok为true,FileExistsErrorPOSIX 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


20

在Python 3.4中,您还可以使用全新的pathlib模块

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.

@JanuszSkonieczny pypi.python.org/pypi/pathlib2是较新的反向端口。较旧的是未维护的。
Acumenus

如自述文件第一行中所述; P。但是旧的backport仍然对这里的答案有效。而且没有命名头痛。无需解释为什么以及何时使用新用户,pathlib以及在何处使用pathlib2新用户,我想这里的专家会解决这个问题;)
Janusz Skonieczny

13

相关的Python文档建议使用的编码风格(更容易请求原谅比许可)EAFP。这意味着代码

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

该文档正是由于此问题中讨论的种族条件而提出了这一建议。此外,正如此处其他人所提到的,查询一次操作系统而不是两次查询操作系统具有性能优势。最后,在某些情况下(当开发人员知道应用程序正在运行的环境时),可能会提出支持第二个代码的参数,只有在特殊情况下才提倡该程序已为该程序建立了私有环境。本身(以及同一程序的其他实例)。

即使在这种情况下,这也是一种不好的做法,并且可能导致长时间的无用调试。例如,我们为目录设置权限的事实不应该使我们拥有为我们目的而适当设置的印象权限。可以使用其他权限挂载父目录。通常,程序应始终正常运行,并且程序员不应期望一个特定的环境。


11

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

os.makedirs(path,exist_ok=True)

Python2中os.makedirs不支持设置exist_ok。您可以在heikki-toivonen的答案中使用该方法:

import os
import errno

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

11

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

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

文档中确保目录存在。如果不存在,请尝试创建它,并在其他进程正在这样做的情况下防止出现竞争情况。


新的IPython文档在此处提供
jkdev

3
IPython绝对不能保证该模块存在。它本地存在于我的Mac上,但不存在于我的任何Linux安装的Python中。基本上,它不是Python Module Index中列出的模块之一
Acumenus '16

1
当然。为了安装该软件包,只需运行常规软件包pip install ipython或在您的requirements.txtpom.xml中包含依赖项。文档:ipython.org/install.html
tashuhka

9

您可以使用 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。


2
distutils.dir_util不是distutil公共API的一部分,并且在多线程环境中存在问题:bugs.python.org/issue10948
Pod

1
是。如该错误的第一条消息所述,问题distutils.dir_util.mkpath在于,如果您创建了一个目录,然后从Python的内部或外部删除了该目录,然后mkpath再次使用,mkpath将仅使用其先前已创建目录的无效缓存信息,并且实际上不重新建立目录。相反,os.makedirs不依赖任何此类缓存。
Acumenus

8

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

它提示用户输入目录,并且可以轻松修改。



6

我找到了这个问题,起初我为自己遇到的一些失败和错误感到困惑。我正在使用Python 3(Arch Linux x86_64系统上的Anaconda虚拟环境中的v.3.5)。

考虑以下目录结构:

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

这是我的实验/注释,它们使事情变得清晰:

# ----------------------------------------------------------------------------
# [1] /programming/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] 如果目录不存在,如何创建?

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


6

我看到了Heikki ToivonenABB的答案,并想到了这种变化。

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


5

如果在支持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...

4

如果考虑以下因素:

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

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


这如何回答创建目录的问题?
乔治,


3

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

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)

这对我有用,希望对您也一样


1
import os
if os.path.isfile(filename):
    print "file exists"
else:
    "Your code here"

您的代码在哪里使用(touch)命令

这将检查文件是否存在,如果不存在则将创建它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.