如何安全地创建嵌套目录?
Posted
技术标签:
【中文标题】如何安全地创建嵌套目录?【英文标题】:How can I safely create a nested directory? 【发布时间】:2010-09-21 08:28:07 【问题描述】:检查文件要写入的目录是否存在,如果不存在,使用 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)
是否有open()
的标志,可以自动执行此操作?
【问题讨论】:
通常您可能需要考虑文件名中没有目录的情况。在我的机器上 dirname('foo.txt') 给出 '',它不存在并导致 makedirs() 失败。 如果路径存在,不仅要检查它是否是目录而不是常规文件或其他对象(许多答案检查这个),还需要检查它是否可写(我做过没有找到检查这个的答案) 如果你来这里创建文件路径字符串p
的父目录,这里是我的代码sn-p:os.makedirs(p[:p.rindex(os.path.sep)], exist_ok=True)
【参考方案1】:
这可能无法准确回答问题。但我猜你的真正意图是创建一个文件及其父目录,因为它的内容全部在 1 个命令中。
您可以通过 fastcore
扩展 pathlib 来做到这一点:path.mk_write(data)
from fastcore.utils import Path
Path('/dir/to/file.txt').mk_write('Hello World')
在fastcore documentation中查看更多信息
【讨论】:
【参考方案2】:在我对在 Python 中使用目录时遇到的一些失败和错误感到困惑之后,我发现了这个 Q/A。我正在使用 Python 3(Arch Linux x86_64 系统上的 Anaconda 虚拟环境中的 v.3.5)。
考虑这个目录结构:
└── output/ ## dir
├── corpus ## file
├── corpus2/ ## dir
└── subdir/ ## dir
这是我的实验/笔记,它澄清了一些事情:
# ----------------------------------------------------------------------------
# [1] https://***.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 in Python?
[2]https://docs.python.org/3/library/os.html#os.makedirs
【讨论】:
【参考方案3】:如果您将文件写入变量路径,您可以在文件路径上使用它来确保创建父目录。
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_file
是 file.ext
(零目录深度)也可以工作。
参见pathlib.PurePath.parent 和pathlib.Path.mkdir。
【讨论】:
【参考方案4】:对于单行解决方案,您可以使用IPython.utils.path.ensure_dir_exists()
:
from IPython.utils.path import ensure_dir_exists
ensure_dir_exists(dir)
来自documentation:确保目录存在。如果它不存在,请尝试创建它并在另一个进程正在执行相同操作时防止出现竞争条件。
IPython 是一个扩展包,不是标准库的一部分。
【讨论】:
【参考方案5】:从 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
...
【讨论】:
【参考方案6】:Linux下可以一行创建目录:
import os
os.system("mkdir -p 0".format('mydir'))
【讨论】:
【参考方案7】:在 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)
如 cmets 和其他地方所述,存在竞争条件 - 如果在 os.path.exists
和 os.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.
【讨论】:
竞争条件很好,但***.com/questions/273192/#273208 中的方法将掩盖创建目录的失败。不要因为投反对票而感到难过——你不喜欢这个答案。这就是投票的目的。 记住 os.path.exists() 不是免费的。如果正常情况是该目录将存在,那么它不存在的情况应作为异常处理。换句话说,尝试打开并写入您的文件,捕获 OSError 异常,并根据 errno,执行您的 makedir() 并重新尝试或重新引发。这会造成代码重复,除非您将编写内容包装在本地方法中。os.path.exists
还为文件返回True
。我已经发布了一个答案来解决这个问题。
正如此处其他答案的评论者所指出的,os.makedirs()
的 exists_ok
参数可用于涵盖如何处理路径的先前存在,因为 Python 3.2。
os.mkdirs()
可能会在路径分隔符被意外遗漏时创建意外文件夹,当前文件夹与预期不符,路径元素包含路径分隔符。如果您使用os.mkdir()
,这些错误将引发异常,提醒您它们的存在。【参考方案8】:
你必须在创建目录之前设置完整路径:
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)
这对我有用,希望对你也有用
【讨论】:
【参考方案9】:如果在支持命令的机器上运行,为什么不使用子进程模块
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...
【讨论】:
【参考方案10】:使用此命令检查并创建目录
if not os.path.isdir(test_img_dir):
os.mkdir(test_img_dir)
【讨论】:
【参考方案11】:检查os.makedirs
:(确保完整路径存在。)
要处理目录可能存在的事实,请捕获 OSError
。
(如果exist_ok
是False
(默认值),如果目标目录已经存在,则会引发OSError
。)
import os
try:
os.makedirs('./path/to/somewhere')
except OSError:
pass
【讨论】:
使用 try/except,您将掩盖目录创建中的错误,以防目录不存在但由于某种原因您无法创建目录【参考方案12】: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
不依赖任何此类缓存。对于某些应用程序,此限制可能没问题。
关于目录的模式,有兴趣的可以参考文档。
【讨论】:
据我所知,这个答案几乎涵盖了所有特殊情况。我计划将其包装在“如果不是 os.path.isdir()”中,因为我希望该目录几乎每次都存在,并且我可以通过这种方式避免异常。 @CharlesL。如果您的原因是性能,则异常可能比检查的磁盘 IO 便宜。 @jpmc26 但 makedirs 在仅检查抛出 OSError 时会执行额外的 stat、umask、lstat。 这是错误的答案,因为它引入了潜在的 FS 种族条件。请参阅 Aaron Hall 的回答。 正如@sleepycal 所说,这与接受的答案有类似的竞争条件。如果在引发错误和检查os.path.isdir
之间,其他人删除了该文件夹,您将引发该文件夹存在的错误、过时和令人困惑的错误。【参考方案13】:
在程序/项目的入口点调用函数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')
【讨论】:
【参考方案14】:我使用os.path.exists()
,here 是一个 Python 3 脚本,可用于检查目录是否存在,如果不存在则创建一个,如果存在则删除它(如果需要)。
提示用户输入目录,方便修改。
【讨论】:
【参考方案15】:我看到Heikki Toivonen 和A-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
【讨论】:
【参考方案16】: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
文档正是因为这个问题中讨论的竞争条件而提出了这一点。此外,正如其他人在此处提到的那样,查询一次而不是两次操作系统具有性能优势。最后,在某些情况下可能支持第二个代码的论点——当开发人员知道应用程序正在运行的环境时——只能在程序设置了私有环境的特殊情况下被提倡。本身(以及同一程序的其他实例)。
即使在这种情况下,这也是一种不好的做法,可能会导致长时间无用的调试。例如,我们为目录设置权限这一事实不应该给我们留下印象权限是为我们的目的适当设置的。可以使用其他权限安装父目录。一般来说,程序应该始终正确运行,程序员不应该期望一个特定的环境。
【讨论】:
【参考方案17】:您可以为此使用os.listdir
:
import os
if 'dirName' in os.listdir('parentFolderPath')
print('Directory Exists')
【讨论】:
【参考方案18】:检查目录是否存在并在必要时创建?
对此的直接回答是,假设您不希望其他用户或进程弄乱您的目录的简单情况:
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
有一个新的 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
的实例。
【讨论】:
【参考方案19】:import os
if os.path.isfile(filename):
print "file exists"
else:
"Your code here"
您的代码在这里使用(触摸)命令
这将检查文件是否存在,如果不存在则创建它。
【讨论】:
【参考方案20】:使用 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 从技术上讲,如果另一个程序正在修改您的程序的目录和文件,那么您的整个程序就是一个巨大的竞争条件。在代码创建它之后和实际将文件放入其中之前,如何阻止另一个程序删除该目录?【参考方案21】:
在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
【讨论】:
【参考方案22】:如果您考虑以下情况:
os.path.isdir('/tmp/dirname')
表示存在目录(路径)并且是目录。所以对我来说,这种方式可以满足我的需要。所以我可以确保它是文件夹(不是文件)并且存在。
【讨论】:
【参考方案23】:您可以使用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。
【讨论】:
【参考方案24】:了解这种情况的具体情况
您在特定路径中提供特定文件,然后从文件路径中提取目录。然后在确保您拥有该目录之后,您尝试打开一个文件进行读取。要评论此代码:
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)
【讨论】:
【参考方案25】:试试os.path.exists
函数
if not os.path.exists(dir):
os.mkdir(dir)
【讨论】:
【参考方案26】:在 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.
【讨论】:
【参考方案27】:我个人建议您使用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
的目录。
【讨论】:
【参考方案28】:我已经记下了以下内容。不过这也不是万无一失的。
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
现在正如我所说,这并不是万无一失的,因为我们有可能无法创建目录,并且在此期间有另一个进程创建它。
【讨论】:
以上是关于如何安全地创建嵌套目录?的主要内容,如果未能解决你的问题,请参考以下文章