Pytanie Jak bezpiecznie utworzyć zagnieżdżony katalog w Pythonie?


Jaki jest najbardziej elegancki sposób sprawdzenia, czy katalog, w którym ma być zapisany plik, istnieje, a jeśli nie, utwórz katalog przy pomocy Pythona? Oto, co próbowałem:

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)

Jakoś tęskniłem os.path.exists (dzięki kanja, Blair i Douglas). Oto, co mam teraz:

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

Czy istnieje flaga "otwarta", która powoduje, że dzieje się to automatycznie?


2989
2017-11-07 18:56


pochodzenie


Ogólnie może być konieczne uwzględnienie przypadku, w którym nie ma katalogu w nazwie pliku. Na moim komputerze dirname ('foo.txt') daje '', która nie istnieje i powoduje, że makedirs () zawodzi. - Brian Hawkins
W python 2.7 os.path.mkdir nie istnieje. Jego os.mkdir. - drevicko
jeśli ścieżka istnieje nie tylko do sprawdzenia, czy jest to katalog, a nie zwykły plik lub inny obiekt (wiele odpowiedzi to sprawdza), konieczne jest również sprawdzenie, czy jest on zapisywalny (nie znalazłem odpowiedzi, która to sprawdzała) - miracle173
W przypadku, gdy przyszedłeś tutaj, aby utworzyć katalogi nadrzędne ciągów plików ptutaj jest mój fragment kodu: os.makedirs(p[:p.rindex(os.path.sep)], exist_ok=True) - Thamme Gowda
meta dyskusja odpowiedzi w tym pytaniu - gnat


Odpowiedzi:


Widzę dwie odpowiedzi z dobrymi cechami, z których każda ma niewielką wadę, więc podam moje zdanie:

Próbować os.path.existsi rozważ os.makedirs do stworzenia.

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

Jak zauważono w komentarzach i gdziekolwiek indziej, istnieje warunek wyścigu - jeśli katalog jest tworzony między os.path.exists i os.makedirs wzywa, os.makedirs zawiedzie z OSError. Niestety, łapie za koc OSError i kontynuowanie nie jest niezawodne, ponieważ zignoruje niepowodzenie w utworzeniu katalogu z powodu innych czynników, takich jak niewystarczające uprawnienia, pełny dysk itp.

Jedną z opcji byłoby złapanie pułapki OSError i sprawdź osadzony kod błędu (patrz Czy istnieje międzyplatformowy sposób na uzyskanie informacji z Python OSError):

import os, errno

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

Ewentualnie może być sekunda os.path.exists, ale przypuśćmy, że inny utworzył katalog po pierwszym sprawdzeniu, a następnie usunął go przed drugim - wciąż można nas oszukać.

W zależności od aplikacji, niebezpieczeństwo równoległych operacji może być mniejsze lub mniejsze niż niebezpieczeństwo stwarzane przez inne czynniki, takie jak uprawnienia do plików. Programista powinien wiedzieć więcej o konkretnej aplikacji i oczekiwanym środowisku przed wyborem implementacji.


3691
2017-11-07 19:06



zgodzili się na próbę / z wyjątkiem rozwiązania jest lepsze - Corey Goldberg
Pamiętaj, że os.path.exists () nie jest bezpłatny. Jeśli normalnie jest to, że katalog będzie tam, to przypadek, w którym nie jest, powinien być traktowany jako wyjątek. Innymi słowy, spróbuj otworzyć i napisać do pliku, złap wyjątek OSError i, na podstawie errno, wykonaj makedir () i spróbuj ponownie lub przebijaj ponownie. Powoduje to duplikowanie kodu, chyba że zawiniesz zapis w metodzie lokalnej. - Andrew
os.path.exists również zwraca True dla pliku. Wysłałem odpowiedź, aby rozwiązać ten problem. - A-B-B
os.mkdirs() może utworzyć niezamierzone foldery, jeśli separator ścieżek zostanie przypadkowo pominięty, bieżący folder nie jest zgodny z oczekiwaniami, a element ścieżki zawiera separator ścieżek. Jeśli użyjesz os.mkdir() te błędy podniosą wyjątek, ostrzegając o ich istnieniu. - drevicko
więc właściwa odpowiedź powinna być stackoverflow.com/questions/273192/... - user3885927


Python 3.5+:

import pathlib
pathlib.Path('/my/directory').mkdir(parents=True, exist_ok=True) 

pathlib.Path.mkdir w powyższym przykładzie rekursywnie tworzy katalog i nie zgłasza wyjątku, jeśli katalog już istnieje. Jeśli nie potrzebujesz lub chcesz, aby rodzice zostali stworzeni, pomiń parents argument.

Python 3.2+:

Za pomocą pathlib:

Jeśli możesz, zainstaluj prąd pathlib nazwano backport pathlib2. Nie instaluj starszego nieobsługiwanego backportu o nazwie pathlib. Następnie zapoznaj się z sekcją Python 3.5+ powyżej i użyj jej tak samo.

Jeśli korzystasz z Pythona 3.4, nawet jeśli jest on dostępny pathlibbrakuje tego, co przydatne exist_ok opcja. Backport ma na celu zaoferować nowszą i lepszą implementację mkdir który zawiera tę brakującą opcję.

Za pomocą os:

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

os.makedirs w powyższym przykładzie rekursywnie tworzy katalog i nie zgłasza wyjątku, jeśli katalog już istnieje. Ma opcjonalne exist_ok argument tylko wtedy, gdy używasz Python 3.2+, z domyślną wartością False. Ten argument nie istnieje w Pythonie 2.x do 2.7. W związku z tym nie ma potrzeby ręcznego obsługiwania wyjątków, tak jak w Pythonie 2.7.

Python 2.7+:

Za pomocą pathlib:

Jeśli możesz, zainstaluj prąd pathlib nazwano backport pathlib2. Nie instaluj starszego nieobsługiwanego backportu o nazwie pathlib. Następnie zapoznaj się z sekcją Python 3.5+ powyżej i użyj jej tak samo.

Za pomocą os:

import os
try: 
    os.makedirs(path)
except OSError:
    if not os.path.isdir(path):
        raise

Chociaż naiwne rozwiązanie może najpierw użyć os.path.isdir śledzony przez os.makedirspowyższe rozwiązanie odwraca kolejność dwóch operacji. W ten sposób zapobiega zwykłemu wyścigowi mając do czynienia z powieloną próbą utworzenia katalogu, a także rozróżnia pliki z katalogów.

Zwróć uwagę, że przechwytywanie wyjątku i używanie errno ma ograniczoną przydatność, ponieważ OSError: [Errno 17] File exists, tj. errno.EEXIST, jest wywoływany zarówno dla plików, jak i katalogów. Bardziej wiarygodne jest po prostu sprawdzenie, czy katalog istnieje.

Alternatywny:

mkpath tworzy zagnieżdżony katalog i nic nie robi, jeśli katalog już istnieje. Działa to zarówno w Pythonie 2, jak i 3.

import distutils.dir_util
distutils.dir_util.mkpath(path)

Za Błąd 10948Poważnym ograniczeniem tej alternatywy jest to, że działa ona tylko raz na proces Pythona dla danej ścieżki. Innymi słowy, jeśli użyjesz go do stworzenia katalogu, usuń katalog z wnętrza lub spoza Pythona, a następnie użyj mkpath ponownie, aby odtworzyć ten sam katalog, mkpath będzie po cichu używał swoich niepoprawnych informacji w pamięci podręcznej o tym, że wcześniej utworzył katalog i nie będzie go ponownie tworzył. W przeciwieństwie, os.makedirs nie polega na takiej pamięci podręcznej. To ograniczenie może być w porządku w przypadku niektórych aplikacji.


W odniesieniu do katalogu tryb, zapoznaj się z dokumentacją, jeśli Ci na tym zależy.


815
2018-01-16 17:31



Ta odpowiedź dotyczy praktycznie każdego przypadku specjalnego, o ile wiem. Planuję zawinąć to w "if not os.path.isdir ()", ale ponieważ oczekuję, że katalog istnieje prawie za każdym razem i mogę uniknąć wyjątku w ten sposób. - Charles L.
@CharlesL. Wyjątek jest prawdopodobnie tańszy niż dysk IO czeku, jeśli twoim powodem jest wydajność. - jpmc26
@ jpmc26, ale makedirs wykonuje dodatkowe statystyki, umask, lstat, gdy tylko sprawdza, czy rzucić OSError. - kwarunek
Jest to zła odpowiedź, ponieważ wprowadza potencjalny wyścig rasy FS. Zobacz odpowiedź od Aarona Hall. - sleepycal
jak powiedział @sleepycal, cierpi na podobny stan wyścigu, jak przyjęta odpowiedź. Jeśli między podniesieniem błędu a sprawdzeniem os.path.isdir ktoś inny usunie ten folder, podniosą Państwo błędny, nieaktualny i mylący błąd tego folderu. - farmir


Używanie try except i poprawny kod błędu z modułu errno pozbywa się warunku wyścigu i jest wieloplatformowy:

import os
import errno

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

Innymi słowy, staramy się tworzyć katalogi, ale jeśli już istnieją, ignorujemy błąd. Z drugiej strony zgłaszany jest każdy inny błąd. Na przykład, jeśli wcześniej utworzysz dir "a" i usuniesz wszystkie uprawnienia, otrzymasz OSError podbicie z errno.EACCES (Odmowa uprawnień, błąd 13).


573
2018-02-17 17:17



Przyjęta odpowiedź jest rzeczywiście niebezpieczna, ponieważ ma stan wyścigowy. Jest to jednak prostsze, więc jeśli nie zdajesz sobie sprawy z warunków wyścigu lub uważasz, że nie będzie to miało zastosowania do ciebie, byłby to twój pierwszy oczywisty wybór. - Heikki Toivonen
Zgłaszanie wyjątku tylko wtedy, gdy exception.errno != errno.EEXIST niechcący zignoruje przypadek, gdy ścieżka istnieje, ale jest obiektem niezalogowanym, takim jak plik. Wyjątek powinien idealnie zostać podniesiony, jeśli ścieżka jest obiektem niezalogowanym. - A-B-B
Pamiętaj, że powyższy kod jest równoważny os.makedirs(path,exist_ok=True) - Navin
@Navin The exist_ok parametr został wprowadzony w Pythonie 3.2. Nie ma go w Pythonie 2.x. Włączę to do mojej odpowiedzi. - A-B-B
@HeikkiToivonen Technicznie rzecz biorąc, jeśli inny program modyfikuje katalogi i pliki w tym samym czasie, co twój program, cały twój program to jeden wielki wyścig. Co powstrzyma inny program przed usunięciem tego katalogu po stworzeniu go przez kod i przed umieszczeniem w nim plików? - jpmc26


Osobiście polecam używanie os.path.isdir() zamiast testować 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

Jeśli masz:

>>> dir = raw_input(":: ")

I głupie dane użytkownika:

:: /tmp/dirname/filename.etc

... W końcu otrzymasz katalog o nazwie filename.etc kiedy przekazujesz ten argument os.makedirs() jeśli testujesz os.path.exists().


85
2018-01-14 17:57



Jeśli używasz tylko "isdir", czy nadal nie będziesz miał problemu podczas próby utworzenia katalogu, a plik o tej samej nazwie już istnieje? - MrWonderful
@MrWonderful Wynikowy wyjątek podczas tworzenia katalogu w istniejącym pliku poprawnie odzwierciedliłby problem z powrotem do wywołującego. - Damian Yerrick
To jest zła rada. Widzieć stackoverflow.com/a/5032238/763269. - Chris Johnson


Czek os.makedirs: (Zapewnia pełną ścieżkę).
 Aby obsłużyć fakt, że katalog może istnieć, przechwyć OSError. (Jeśli exist_ok jest Fałszem (domyślnie), to OSError jest wywoływany, jeśli katalog docelowy już istnieje.)

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

56
2017-11-07 19:01



przy próbie / oprócz tego, będziesz maskował błędy w tworzeniu katalogu, w przypadku gdy katalog nie istnieje, ale z jakiegoś powodu nie możesz go wykonać - Blair Conrad
To jedyny bezpieczny sposób. - Ali Afshar
OSError zostanie podniesione tutaj, jeśli ścieżka jest istniejącym plikiem lub katalogiem. Wysłałem odpowiedź, aby rozwiązać ten problem. - A-B-B
To jest w połowie drogi. Musisz sprawdzić stan błędu podrzędnego OSError przed podjęciem decyzji o zignorowaniu go. Widzieć stackoverflow.com/a/5032238/763269. - Chris Johnson


Wgląd w specyfikę tej sytuacji

Podajesz określony plik w określonej ścieżce i wyciągasz katalog ze ścieżki do pliku. Następnie po upewnieniu się, że posiadasz katalog, spróbujesz otworzyć plik do odczytu. Aby skomentować ten kod:

filename = "/my/directory/filename.txt"
dir = os.path.dirname(filename)

Chcemy uniknąć nadpisania wbudowanej funkcji, dir. Również, filepath lub może fullfilepath jest prawdopodobnie lepszą nazwą semantyczną niż filename więc byłoby lepiej napisane:

import os
filepath = '/my/directory/filename.txt'
directory = os.path.dirname(filepath)

Twoim głównym celem jest otwarcie tego pliku, początkowo o tym piszesz, ale zasadniczo zbliżasz się do tego celu (w oparciu o twój kod) w ten sposób, który otwiera plik dla czytanie:

if not os.path.exists(directory):
    os.makedirs(directory)
f = file(filename)

Zakładając otwarcie do czytania

Dlaczego miałbyś utworzyć katalog dla pliku, który spodziewałbyś się tam znaleźć i móc czytać?

Po prostu spróbuj otworzyć plik.

with open(filepath) as my_file:
    do_stuff(my_file)

Jeśli katalog lub plik nie istnieje, dostaniesz IOError z powiązanym numerem błędu: errno.ENOENT wskaże prawidłowy numer błędu niezależnie od platformy. Możesz go złapać, jeśli chcesz, na przykład:

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

Zakładając, że otwieramy się do pisania

To jest prawdopodobnie czego chcesz.

W tym przypadku prawdopodobnie nie mamy żadnych warunków wyścigowych. Po prostu rób tak jak Ty, ale pamiętaj, że aby pisać, musisz otworzyć przy pomocy w tryb (lub a dołączyć). Jest to również najlepsza praktyka Pythona do używania menedżera kontekstu do otwierania plików.

import os
if not os.path.exists(directory):
    os.makedirs(directory)
with open(filepath, 'w') as my_file:
    do_stuff(my_file)

Powiedzmy jednak, że mamy kilka procesów Pythona, które próbują umieścić wszystkie swoje dane w tym samym katalogu. Wtedy możemy mieć spór o stworzenie katalogu. W takim przypadku najlepiej jest zawijać makedirs wywołaj blok 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)

29
2018-01-22 23:49





Począwszy od wersji Python 3.5, pathlib.Path.mkdir ma exist_ok flaga:

from pathlib import Path
path = Path('/my/directory/filename.txt')
path.parent.mkdir(parents=True, exist_ok=True) 
# path.parent ~ os.path.dirname(path)

To rekursywnie tworzy katalog i nie wywołuje wyjątku, jeśli katalog już istnieje.

(tak jak os.makedirs dostałem exists_ok flag począwszy od python 3.2).


28
2017-12-14 16:06





Złożyłem poniższe. Nie jest to jednak całkowicie niezawodne.

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

Teraz, jak mówię, nie jest to naprawdę niezawodne, ponieważ mamy możliwość nieudanego utworzenia katalogu i innego procesu tworzenia go w tym okresie.


22
2017-11-07 21:23



stackoverflow.com/questions/273698/... - Ali Afshar
Dwa problemy: (1) przed podjęciem decyzji o sprawdzeniu należy sprawdzić warunek błędu podrzędnego OSError os.path.exists - patrz stackoverflow.com/a/5032238/763269 i (2) powodzenie na os.path.exists nie oznacza, że ​​katalog istnieje, tylko że ścieżka istnieje - może to być plik, dowiązanie symboliczne lub inny obiekt systemu plików. - Chris Johnson