Питання Як можна безпечно створити вкладений каталог у 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)

Чи є прапор "відкритим", що робить це автоматично?


2989
2017-11-07 18:56


походження


Загалом вам може знадобитися облік випадку, коли в імені файлу немає каталогу. На моїй машині dirname ('foo.txt') дає '', який не існує і викликає помилку makedirs (). - Brian Hawkins
У python 2.7 os.path.mkdir не існує Його os.mkdir. - drevicko
якщо існує шлях, потрібно не тільки перевірити, чи це каталог, а не звичайний файл або інший об'єкт (багато відповідей перевіряють це), також необхідно перевірити, чи він доступний для запису (я не знайшов відповіді, яка перевірила це) - miracle173
У випадку, якщо ви прийшли сюди, щоб створити батьківські каталоги рядка шляху до файлу p, це мій фрагмент коду: os.makedirs(p[:p.rindex(os.path.sep)], exist_ok=True) - Thamme Gowda
мета обговорення відповідей у ​​цьому питанні - gnat


Відповіді:


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

Спробуй 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 і перевірте вбудований код помилки (див Чи існує крос-платформенний спосіб отримання інформації з OSError Python):

import os, errno

try:
    os.makedirs(directory)
except OSError as e:
    if e.errno != errno.EEXIST:
        raise

Крім того, може бути секунда os.path.exists, але припустімо, що інший створив каталог після першої перевірки, після чого видалив його перед другим - все-таки ми могли б обдуритись.

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


3691
2017-11-07 19:06



погодився спроба / окрім рішення краще - Corey Goldberg
Пам'ятайте, що os.path.exists () не є вільним. Якщо звичайним є те, що каталог буде там, то справа, де вона не є, повинна розглядатися як виняток. Іншими словами, спробуйте відкрити та записати у свій файл, спіймайте виняток OSError, і, виходячи з errno, виконайте свій македір () і повторно спробуйте або повторно підніміть. Це створює копіювання коду, якщо ви не обертаєте запис у місцевому методі. - Andrew
os.path.exists також повертається True для файлу Я надіслав відповідь на це питання. - A-B-B
os.mkdirs() може створювати непередбачені папки, якщо розділений шлях випадково не вийшов, поточна папка не така, як очікувалося, елемент шляху містить розділювач шляхів. Якщо ви використовуєте os.mkdir() ці помилки піднімуть виняток, попереджуючи вас про їх існування. - drevicko
тому реальна відповідь повинна бути stackoverflow.com/questions/273192/... - user3885927


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 не покладатися на такий кеш. Це обмеження може бути в порядку для деяких програм.


Що стосується директорії режим, будь ласка, зверніться до документації, якщо вам це важливо.


815
2018-01-16 17:31



Ця відповідь охоплює майже кожен окремий випадок, наскільки я можу сказати. Я планую упаковувати це в "if not os.path.isdir ()", хоча, оскільки я очікую, що каталог існує майже кожен раз, я можу уникнути виключення таким чином. - Charles L.
@ CharlesL Виняток, ймовірно, дешевше, ніж диск IO перевірки, якщо ваша причина - продуктивність. - jpmc26
@ jpmc26, але македарі робить додатковий статут, umask, lstat, лише перевіряючи, щоб кинути OSError. - kwarunek
Це неправильна відповідь, оскільки вона вводить потенційну змагання з FS. Подивіться відповідь від Аарона Холла. - sleepycal
як сказав @sleepycal, це страждає від аналогічного стану перегонів, як прийнята відповідь. Якщо між підвищенням помилки та перевірки os.path.isdir хтось видаляє папку, ви піднімете неправильну, застарілу та заплутану помилку, яка існує в цій папці. - farmir


Використовуючи спробу крім і правий код помилки з модуля 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).


573
2018-02-17 17:17



Прийнята відповідь є насправді небезпечною, оскільки вона має рас-умови. Це простіше, однак, тому що, якщо ви не знаєте про стану стану, або думаєте, що це не стосується вас, це буде вашим першим вибором. - Heikki Toivonen
Підвищення винятку лише тоді, коли exception.errno != errno.EEXIST буде ненавмисно ігнорувати той випадок, коли існує шлях, але є об'єктом, що не є каталогом, такий як файл. Виняток ідеально підійметься, якщо шлях є об'єктом, що не є каталогом. - A-B-B
Зауважте, що наведений вище код еквівалентний os.makedirs(path,exist_ok=True) - Navin
@Навін exist_ok Параметр був введений в Python 3.2. В Python 2.x він відсутній. Я додам його до моєї відповіді. - A-B-B
@ HeikkiToivonen Технічно кажучи, якщо інша програма модифікує каталоги та файли одночасно з вашою програмою, вся ваша програма є одним гігантським станом перегонів. Що зупинити іншу програму, просто видаляючи цю директорію після того, як код створить його і перед тим, як ви насправді розмістите в ньому файли? - jpmc26


Я особисто рекомендую вам використовувати 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().


85
2018-01-14 17:57



Якщо ви використовуєте лише "isdir", чи не виникнуть проблеми при спробі створення каталогу, а файл з тим самим ім'ям вже існує? - MrWonderful
@MrWonderful Винятковий виняток при створенні каталогу над існуючим файлом правильно відображає проблему тому, хто викликає. - Damian Yerrick
Це погана порада. Побачити stackoverflow.com/a/5032238/763269. - Chris Johnson


Перевірте ом.mакедир: (Це гарантує наявність повного шляху.)
 Щоб справитися з тим, що каталог може існувати, спіймайте OSError. (Якщо існує_ок є False (за замовчуванням), OSError піднімається, якщо цільова директорія вже існує.)

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

56
2017-11-07 19:01



при спробі / за винятком, ви будете маскувати помилки при створенні каталогу, у випадку, коли каталог не існує, але чомусь ви не можете це зробити - Blair Conrad
Це єдиний безпечний спосіб. - Ali Afshar
OSError буде порушено тут, якщо шлях є існуючим файлом або каталогом. Я надіслав відповідь на це питання. - A-B-B
Це на півдорозі. Вам потрібно перевірити стан підпункту помилки OSError перш ніж прийняти рішення про це ігнорувати. Побачити stackoverflow.com/a/5032238/763269. - Chris Johnson


Погляд на специфіку цієї ситуації

Ви даєте певний файл на певному шляху, і ви витягуєте каталог з шляху до файлу. Потім, переконавшись, що у вас є каталог, ви намагаєтеся відкрити файл для читання. Коментувати цей код:

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 зателефонуйте в проб-окрім блоку.

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)

29
2018-01-22 23:49





Починаючи з 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).


28
2017-12-14 16:06





Я поставив наступне. Однак це не зовсім безглуздо.

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

Тепер, як я кажу, це не дуже надійно, тому що у нас є можливість не створювати каталог, а інший процес створення його протягом цього періоду.


22
2017-11-07 21:23



stackoverflow.com/questions/273698/... - Ali Afshar
Дві проблеми: (1) перед тим, як прийняти рішення про перевірку, вам слід перевірити стан додаткової помилки OSError os.path.exists - див. stackoverflow.com/a/5032238/763269, і (2) успіх os.path.exists не означає, що каталог існує, лише те, що шлях існує - може бути файлом або символічним посиланням або іншим об'єктом файлової системи. - Chris Johnson


Спробуйте os.path.exists функція

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

22
2017-11-07 19:00



Я хотів би прокоментувати це питання, але ми маємо на увазі os.mkdir? Моє python (2.5.2) не має os.path.mkdir .... - Blair Conrad
Немає ніякого os.path.mkdir() метод os.path модуль реалізує кілька корисних функції на шляху. - Serge S.
Це страшна порада. Побачити stackoverflow.com/a/5032238/763269. - Chris Johnson


Перевірте наявність каталогу та створіть його, якщо це необхідно?

Пряма відповідь на це полягає в тому, що просту ситуацію, коли ви не очікуєте, що інші користувачі або процеси потраплять до вашої директорії:

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 зараз - просто приєднуйтесь до шляхових частин з a /:

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 шляхи можуть їх використовувати.

Можливо, Pandas слід оновити, щоб прийняти екземпляри абстрактного базового класу, os.PathLike.


15
2018-01-22 23:45





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

14
2018-03-11 20:50



Це також підтримується в 2.7: pypi.python.org/pypi/pathlib - Janusz Skonieczny
@JanuszSkonieczny він не підтримується в Python 2.7; тільки це було підтримано там. У Python 3.4 це вбудований модуль. - Antti Haapala
І яка різниця між підтримкою та підтримкою? Це працює не так? І так, ми все це отримаємо, це не основна бібліотека py 2.7, тому я опублікував посилання на перелік PYPI; P - Janusz Skonieczny
Якщо інформація в коментарях була в відповіді, коментарі будуть застарілими. - Aaron Hall♦
@ JanuszSkonieczny pypi.python.org/pypi/pathlib2 це нове задор. Старший є незмінним. - A-B-B