Как я могу безопасно создать вложенный каталог? - PullRequest
3590 голосов
/ 07 ноября 2008

Какой самый элегантный способ проверить, существует ли каталог, в который будет записан файл, и, если нет, создать каталог с помощью 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)

Есть ли флаг "open", который делает это автоматически?

Ответы [ 26 ]

4394 голосов
/ 07 ноября 2008

Я вижу два ответа с хорошими качествами, каждый с небольшим недостатком, поэтому я дам свой взгляд на него:

Попробуйте os.path.exists и рассмотрите os.makedirs для создания.

import os
if not os.path.exists(directory):
    os.makedirs(directory)

Как отмечено в комментариях и в других местах, есть условие гонки & ndash; если каталог создается между вызовами 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, но предположим, что другой создал каталог после первой проверки, а затем удалил его до второй & ndash; мы все еще можем быть одурачены.

В зависимости от приложения опасность одновременных операций может быть больше или меньше опасности других факторов, таких как права доступа к файлам. Разработчик должен знать больше о конкретном разрабатываемом приложении и его ожидаемой среде, прежде чем выбирать реализацию.

Современные версии 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.
1079 голосов
/ 16 января 2013

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)

Per Ошибка 10948 , серьезным ограничением этой альтернативы является то, что она работает только один раз на процесс python для данного пути. Другими словами, если вы используете его для создания каталога, затем удалите каталог изнутри или снаружи Python, а затем снова используйте mkpath для воссоздания того же каталога, mkpath просто будет молча использовать свою неверную кэшированную информацию о ранее созданном каталог, и фактически не будет делать каталог снова. Напротив, os.makedirs не полагается на такой кеш. Это ограничение может быть приемлемым для некоторых приложений.


Что касается режима каталога , пожалуйста, обратитесь к документации, если вы заботитесь об этом.

590 голосов
/ 17 февраля 2011

Использование 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).

93 голосов
/ 14 января 2009

Я бы лично порекомендовал вам использовать 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().

68 голосов
/ 07 ноября 2008

Проверка os.makedirs: (Он гарантирует, что полный путь существует.)
Чтобы справиться с тем фактом, что каталог может существовать, перехватите OSError. (Если exist_ok равно False (по умолчанию), OSError повышается, если целевой каталог уже существует.)

import os
try:
    os.makedirs('./path/to/somewhere')
except OSError:
    pass
58 голосов
/ 14 декабря 2016

Начиная с 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 получил флаг exist_ok, начиная с python 3.2, например, os.makedirs(path, exist_ok=True))

38 голосов
/ 23 января 2015

Взгляд на специфику этой ситуации

Вы указываете определенный файл по определенному пути и извлекаете каталог из пути к файлу. Затем, убедившись, что у вас есть каталог, вы пытаетесь открыть файл для чтения. Чтобы прокомментировать этот код:

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)
31 голосов
/ 07 ноября 2008

Попробуйте функцию os.path.exists

if not os.path.exists(dir):
    os.mkdir(dir)
28 голосов
/ 08 ноября 2008

Я изложил следующее. Это не совсем надежно, хотя.

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

Теперь, как я уже сказал, это не совсем надежно, потому что у нас есть возможность не создать каталог, и другой процесс создаст его в течение этого периода.

21 голосов
/ 23 января 2015

Проверить, существует ли каталог, и создать его при необходимости?

Прямой ответ на этот вопрос в простой ситуации, когда вы не ожидаете, что другие пользователи или процессы будут связываться с вашим каталогом:

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 имеет значение true, исключения 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, смогут их использовать.

Возможно, Pandas следует обновить, чтобы принимать экземпляры абстрактного базового класса, os.PathLike.

...