Domanda Come posso creare in modo sicuro una directory nidificata in Python?


Qual è il modo più elegante per verificare se la directory in cui un file verrà scritto esiste, e in caso contrario, creare la directory usando Python? Ecco cosa ho provato:

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)

In qualche modo, ho perso os.path.exists (grazie kanja, Blair e Douglas). Questo è quello che ho adesso:

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

C'è una bandiera per "open", che lo fa accadere automaticamente?


2978
2017-11-07 18:56


origine


risposte:


Vedo due risposte con buone qualità, ognuna con un piccolo difetto, quindi darò la mia opinione su questo:

Provare os.path.existse considera os.makedirs per la creazione.

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

Come notato nei commenti e altrove, c'è una condizione di competizione - se la directory viene creata tra il os.path.exists e il os.makedirs chiama, il os.makedirs fallirà con un OSError. Sfortunatamente, la cattura di coperte OSError e continua non è infallibile, poiché ignorerà l'impossibilità di creare la directory a causa di altri fattori, come autorizzazioni insufficienti, disco completo, ecc.

Un'opzione sarebbe quella di intrappolare il OSError ed esaminare il codice di errore incorporato (vedi Esiste un modo multipiattaforma per ottenere informazioni da OSError di Python):

import os, errno

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

In alternativa, potrebbe esserci un secondo os.path.existsma supponiamo che un altro abbia creato la directory dopo il primo controllo, quindi l'abbia rimossa prima della seconda: potremmo ancora essere ingannati.

A seconda dell'applicazione, il pericolo di operazioni concorrenti può essere più o meno del pericolo rappresentato da altri fattori come i permessi dei file. Lo sviluppatore dovrebbe conoscere meglio la particolare applicazione in fase di sviluppo e il suo ambiente previsto prima di scegliere un'implementazione.


3674
2017-11-07 19:06



Python 3.5+:

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

pathlib.Path.mkdir come usato sopra crea ricorsivamente la directory e non genera un'eccezione se la directory esiste già. Se non hai bisogno o vuoi che i genitori vengano creati, salta il parents discussione.

Python 3.2+:

utilizzando pathlib:

Se puoi, installa la corrente pathlib backport chiamato pathlib2. Non installare il vecchio backport non più mantenuto denominato pathlib. Successivamente, consultare la sezione Python 3.5+ sopra e usarla allo stesso modo.

Se si utilizza Python 3.4, anche se viene fornito con pathlibmanca l'utile exist_ok opzione. Il backport è destinato a offrire un'implementazione più recente e superiore di mkdir che include questa opzione mancante.

utilizzando os:

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

os.makedirs come usato sopra crea ricorsivamente la directory e non genera un'eccezione se la directory esiste già. Ha l'opzionale exist_ok argomento solo se si utilizza Python 3.2+, con un valore predefinito di False. Questo argomento non esiste in Python 2.x fino a 2.7. Pertanto, non è necessario gestire manualmente le eccezioni come con Python 2.7.

Python 2.7+:

utilizzando pathlib:

Se puoi, installa la corrente pathlib backport chiamato pathlib2. Non installare il vecchio backport non più mantenuto denominato pathlib. Successivamente, consultare la sezione Python 3.5+ sopra e usarla allo stesso modo.

utilizzando os:

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

Mentre una soluzione ingenua può essere utilizzata per la prima volta os.path.isdir seguito da os.makedirs, la soluzione di cui sopra inverte l'ordine delle due operazioni. In tal modo, impedisce una condizione di competizione comune che ha a che fare con un tentativo duplicato di creare la directory e disambigua anche i file dalle directory.

Si noti che catturare l'eccezione e l'utilizzo errno è di utilità limitata perché OSError: [Errno 17] File exists, cioè errno.EEXIST, viene generato sia per i file che per le directory. È più affidabile semplicemente controllare se la directory esiste.

Alternativa:

mkpath crea la directory nidificata e non fa nulla se la directory esiste già. Funziona sia in Python 2 che in 3.

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

Per Bug 10948, una grave limitazione di questa alternativa è che funziona solo una volta per processo Python per un dato percorso. In altre parole, se lo si utilizza per creare una directory, quindi eliminare la directory da dentro o fuori Python, quindi utilizzare mkpath di nuovo per ricreare la stessa directory, mkpath semplicemente utilizzerà silenziosamente le informazioni cache non valide di aver precedentemente creato la directory e non creerà di nuovo la directory. In contrasto, os.makedirs non si basa su alcuna cache di questo tipo. Questa limitazione potrebbe essere accettabile per alcune applicazioni.


Per quanto riguarda la directory modalità, per favore riferisciti alla documentazione se ti interessa.


808
2018-01-16 17:31



Usando try except e il codice di errore corretto dal modulo errno si sbarazza delle condizioni della gara ed è multipiattaforma:

import os
import errno

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

In altre parole, proviamo a creare le directory, ma se esistono già ignoriamo l'errore. D'altra parte, viene segnalato qualsiasi altro errore. Ad esempio, se crei dir 'a' prima e rimuovi tutti i permessi da esso, otterrai un OSError cresciuto con errno.EACCES (Autorizzazione negata, errore 13).


572
2018-02-17 17:17



Personalmente raccomanderei di usarlo os.path.isdir() testare invece di 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

Se hai:

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

E un input utente folle:

:: /tmp/dirname/filename.etc

... Finirai con una directory chiamata filename.etc quando si passa a questo argomento os.makedirs() se si prova con os.path.exists().


85
2018-01-14 17:57



Dai un'occhiata os.makedirs: (Si assicura che il percorso completo esista.)
 Per gestire il fatto che la directory potrebbe esistere, cattura OSError. (Se exist_ok è False (impostazione predefinita), viene sollevato un OSError se la directory di destinazione esiste già.)

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

56
2017-11-07 19:01



Approfondimenti sulle specificità di questa situazione

Si assegna un determinato file in un determinato percorso e si estrae la directory dal percorso del file. Quindi, dopo essersi assicurati di avere la directory, si tenta di aprire un file per la lettura. Per commentare questo codice:

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

Vogliamo evitare di sovrascrivere la funzione integrata, dir. Anche, filepath o forse fullfilepath è probabilmente un nome semantico migliore di filename quindi questo sarebbe meglio scritto:

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

Il tuo obiettivo finale è aprire questo file, inizialmente lo dichiari, per scrivere, ma ti stai essenzialmente avvicinando a questo obiettivo (basato sul tuo codice) come questo, che apre il file per lettura:

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

Supponendo l'apertura per la lettura

Perché dovresti creare una directory per un file che prevedi di essere lì ed essere in grado di leggere?

Basta provare ad aprire il file.

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

Se la directory o il file non ci sono, otterrai un IOError con un numero di errore associato: errno.ENOENT punterà al numero di errore corretto indipendentemente dalla tua piattaforma. Puoi prenderlo se vuoi, per esempio:

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

Supponendo che stiamo aprendo per scrivere

Questo è probabilmente quello che stai volendo

In questo caso, probabilmente non stiamo affrontando alcuna condizione di gara. Quindi fai come sei, ma nota che per scrivere, devi aprire con w modalità (o a aggiungere). È anche una best practice di Python utilizzare il gestore del contesto per aprire i file.

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

Tuttavia, diciamo che abbiamo diversi processi Python che tentano di inserire tutti i loro dati nella stessa directory. Quindi potremmo avere contese sulla creazione della directory. In tal caso è meglio avvolgere il makedirs chiama un blocco 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



A partire da Python 3.5, pathlib.Path.mkdir ha un exist_ok bandiera:

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

Questo crea in modo ricorsivo la directory e non genera un'eccezione se la directory esiste già.

(proprio come os.makedirs ottenuto un exists_ok bandiera a partire da python 3.2).


28
2017-12-14 16:06



Ho messo giù il seguente. Tuttavia non è totalmente infallibile.

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

Ora come ho detto, questo non è veramente infallibile, perché abbiamo la possibilità di non riuscire a creare la directory, e un altro processo che la crea durante quel periodo.


22
2017-11-07 21:23