Как я могу безопасно создать вложенный каталог в 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
(спасибо, Канджа, Блэр и Дуглас). Вот что у меня сейчас:
def ensure_dir(file_path):
directory = os.path.dirname(file_path)
if not os.path.exists(directory):
os.makedirs(directory)
Есть ли флаг "открыть", который делает это автоматически?
32 ответа
Я вижу два ответа с хорошими качествами, каждый с небольшим недостатком, поэтому я дам свой взгляд на него:
Пытаться 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
, но предположим, что другой создал каталог после первой проверки, а затем удалил его до второй проверки - нас все еще можно обмануть.
В зависимости от приложения опасность одновременных операций может быть больше или меньше опасности других факторов, таких как права доступа к файлам. Разработчик должен знать больше о конкретном разрабатываемом приложении и его ожидаемой среде, прежде чем выбирать реализацию.
Современные версии 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.
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
, Этот аргумент не существует в 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
не полагается на любой такой кэш. Это ограничение может быть хорошо для некоторых приложений.
Что касается режима каталога, пожалуйста, обратитесь к документации, если вы заботитесь о нем.
Использование 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).
Начиная с 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).
Я лично рекомендую вам использовать 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()
,
Проверьте os.makedirs: (Это гарантирует, что полный путь существует.)
Чтобы обработать тот факт, что каталог может существовать, перехватите OSError.
(Если exist_ok имеет значение False (по умолчанию), OSError вызывается, если целевой каталог уже существует.)
import os
try:
os.makedirs('./path/to/somewhere')
except OSError:
pass
Попробуйте os.path.exists
функция
if not os.path.exists(dir):
os.mkdir(dir)
Понимание специфики этой ситуации
Вы указываете конкретный файл по определенному пути и извлекаете каталог из пути к файлу. Затем, убедившись, что у вас есть каталог, вы пытаетесь открыть файл для чтения. Чтобы прокомментировать этот код:
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-Кроме.
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)
Я изложил следующее. Это не совсем надежно, хотя.
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.Path
с exist_ok
Там новый Path
объект (по состоянию на 3.4) с множеством методов, которые можно использовать с путями - один из которых mkdir
,
(Для контекста я отслеживаю своего еженедельного представителя с помощью скрипта. Вот соответствующие части кода из скрипта, которые позволяют мне избегать переполнения стека более одного раза в день для одних и тех же данных.)
Сначала соответствующий импорт:
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
правда,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
перед другими API, которые ожидают str
пути могут использовать их.
Возможно, Панды должны быть обновлены, чтобы принимать экземпляры абстрактного базового класса, os.PathLike
,
Самый быстрый и безопасный способ сделать это: он создаст, если не существует, и пропустит, если существует:
from pathlib import Path
Path("path/with/childs/.../").mkdir(parents=True, exist_ok=True)
Лучший способ сделать это в питоне
#Devil
import os
directory = "./out_dir/subdir1/subdir2"
if not os.path.exists(directory):
os.makedirs(directory)
В 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.
В Python3os.makedirs
поддерживает настройку exist_ok
, Настройка по умолчанию False
, что означает OSError
будет поднят, если целевой каталог уже существует. Установив exist_ok
в True
, OSError
(каталог существует) будет проигнорирован, и каталог не будет создан.
os.makedirs(path,exist_ok=True)
В Python2os.makedirs
не поддерживает настройку exist_ok
, Вы можете использовать подход в ответе Хейкки-Тойвонена:
import os
import errno
def make_sure_path_exists(path):
try:
os.makedirs(path)
except OSError as exception:
if exception.errno != errno.EEXIST:
raise
Для однолинейного решения вы можете использовать IPython.utils.path.ensure_dir_exists()
:
from IPython.utils.path import ensure_dir_exists
ensure_dir_exists(dir)
Из документации: убедитесь, что каталог существует. Если он не существует, попытайтесь создать его и защитить от состояния гонки, если другой процесс делает то же самое.
В соответствующей документации по 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
Документация предполагает это именно из-за состояния гонки, обсуждаемого в этом вопросе. Кроме того, как другие упоминают здесь, есть преимущество в производительности при запросе один раз, а не дважды ОС. Наконец, аргумент, выдвигаемый, возможно, в пользу второго кода в некоторых случаях - когда разработчик знает среду, в которой выполняется приложение - может быть защищен только в особом случае, когда программа создала частную среду для сам (и другие экземпляры той же программы).
Даже в этом случае это плохая практика и может привести к длительной бесполезной отладке. Например, тот факт, что мы устанавливаем права доступа для каталога, не должен оставлять нас с впечатлением, что права доступа установлены для наших целей. Родительский каталог может быть смонтирован с другими разрешениями. В общем, программа должна всегда работать правильно, и программист не должен ожидать одну конкретную среду.
Я нашел это Q/A, и я был первоначально озадачен некоторыми сбоями и ошибками, которые я получал. Я работаю в Python 3 (v.3.5 в виртуальной среде Anaconda в системе Arch Linux x86_64).
Рассмотрим эту структуру каталогов:
└── output/ ## dir
├── corpus ## file
├── corpus2/ ## dir
└── subdir/ ## dir
Вот мои эксперименты / заметки, которые проясняют вещи:
# ----------------------------------------------------------------------------
# [1] https://stackru.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" является более надежным.
Ты можешь использовать 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.
Вы должны установить полный путь перед созданием каталога:
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)
Это работает для меня и, надеюсь, это будет работать и для вас
Вы можете передать параметр Exist_ok=True в функцию os.madeirs(), чтобы подавить ошибку, если каталог уже существует:
import os
# Create directory /path/to/nested/directory if it doesn't already exist
os.makedirs('/path/to/nested/directory', exist_ok=True)
Я использую os.path.exists()
Вот сценарий Python 3, который можно использовать для проверки, существует ли каталог, создать его, если он не существует, и удалить его, если он существует (при желании).
Он предлагает пользователям ввести каталог и может быть легко изменен.
Используйте эту команду, проверьте и создайте каталог
if not os.path.isdir(test_img_dir):
os.mkdir(str("./"+test_img_dir))
Если вы пишете файл по переменному пути, вы можете использовать его в пути к файлу, чтобы убедиться, что родительские каталоги созданы.
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
(нулевая глубина каталогов).
Почему бы не использовать модуль подпроцесса, если он работает на компьютере, который поддерживает языки оболочки? Работает на Python 2.7 и Python 3.6
from subprocess import call
call(['mkdir', '-p', 'path1/path2/path3'])
Должен сделать трюк на большинстве систем.
Я видел ответы Хейкки Тойвонена и АББ и думал об этом варианте.
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
Вызвать функцию 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')
Ты можешь использовать os.listdir
за это:
import os
if 'dirName' in os.listdir('parentFolderPath')
print('Directory Exists')
Если вы считаете следующее:
os.path.isdir('/tmp/dirname')
означает, что каталог (путь) существует И является каталогом. Так что для меня этот путь делает то, что мне нужно. Так что я могу убедиться, что это папка (а не файл) и существует.
При работе с файловым вводом / выводом важно учитывать
TOCTTOU (время проверки ко времени использования)
Так что делать проверку с if
и затем чтение или запись позже могут закончиться необработанным исключением ввода-вывода. Лучший способ сделать это:
try:
os.makedirs(dir_path)
except OSError as e:
if e.errno != errno.EEXIS:
raise
Вы можете создать файл и все его родительские каталоги в одной команде с
fastcore
расширение к pathlib:
path.mk_write(data)
from fastcore.utils import Path
Path('/dir/to/file.txt').mk_write('Hello World')