Vraag Hoe kan ik veilig een geneste directory maken in Python?


Wat is de meest elegante manier om te controleren of de directory waarin een bestand zal worden geschreven, bestaat en als dat niet het geval is, maak dan de map met behulp van Python? Dit is wat ik probeerde:

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)

Op een of andere manier miste ik os.path.exists (bedankt kanja, Blair en Douglas). Dit is wat ik nu heb:

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

Is er een vlag voor "open", waardoor dit automatisch gebeurt?


2983
2017-11-07 18:56


oorsprong


antwoorden:


Ik zie twee antwoorden met goede eigenschappen, elk met een kleine tekortkoming, dus ik zal mijn mening erover geven:

Proberen os.path.existsen overweeg os.makedirs voor de creatie.

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

Zoals opgemerkt in opmerkingen en elders, is er een raceconditie - als de map is aangemaakt tussen de os.path.exists en de os.makedirs oproepen, de os.makedirs zal mislukken met een OSError. Helaas, deken-vangen OSError en verder is niet onfeilbaar, omdat het een falen van het aanmaken van de map zal negeren vanwege andere factoren, zoals onvoldoende rechten, volledige schijf, etc.

Een optie zou zijn om de OSError en onderzoek de ingesloten foutcode (zie Is er een platformonafhankelijke manier om informatie te krijgen van OSError van Python):

import os, errno

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

Als alternatief kan er een tweede zijn os.path.exists, maar stel dat een andere de directory na de eerste controle heeft gemaakt en deze vervolgens voor de tweede heeft verwijderd - we konden nog steeds voor de gek gehouden worden.

Afhankelijk van de toepassing kan het gevaar van gelijktijdige bewerkingen meer of minder zijn dan het gevaar van andere factoren, zoals bestandsmachtigingen. De ontwikkelaar moet meer weten over de specifieke applicatie die wordt ontwikkeld en de verwachte omgeving voordat hij een implementatie kiest.


3679
2017-11-07 19:06



Python 3.5+:

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

pathlib.Path.mkdir zoals hierboven gebruikt maakt recursief de map aan en roept geen uitzondering op als de map al bestaat. Als u de ouders niet nodig heeft of wilt laten maken, sla dan de parents argument.

Python 3.2+:

Gebruik makend van pathlib:

Als je kunt, installeer dan de stroom pathlib backport genaamd pathlib2. Installeer de oudere niet-onderhouden backportnaam niet pathlib. Bekijk vervolgens het gedeelte Python 3.5+ hierboven en gebruik het hetzelfde.

Bij gebruik van Python 3.4, ook al wordt het meegeleverd pathlib, het mist het nuttige exist_ok keuze. De backport is bedoeld om een ​​nieuwere en superieure implementatie van te bieden mkdir welke deze ontbrekende optie bevat.

Gebruik makend van os:

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

os.makedirs zoals hierboven gebruikt maakt recursief de map aan en roept geen uitzondering op als de map al bestaat. Het heeft de optionele exist_ok argument alleen als Python 3.2+ wordt gebruikt, met een standaardwaarde van False. Dit argument bestaat niet in Python 2.x tot 2,7. Als zodanig is er geen noodzaak voor handmatige uitzonderingsbehandeling zoals met Python 2.7.

Python 2.7+:

Gebruik makend van pathlib:

Als je kunt, installeer dan de stroom pathlib backport genaamd pathlib2. Installeer de oudere niet-onderhouden backportnaam niet pathlib. Bekijk vervolgens het gedeelte Python 3.5+ hierboven en gebruik het hetzelfde.

Gebruik makend van os:

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

Hoewel een naïeve oplossing voor het eerst kan gebruiken os.path.isdir gevolgd door os.makedirs, de bovenstaande oplossing keert de volgorde van de twee bewerkingen om. Door dit te doen, voorkomt het een algemene race-conditie die te maken heeft met een dubbele poging om de directory aan te maken, en disambigueert het ook bestanden uit mappen.

Merk op dat je de uitzondering vastlegt en gebruikt errno is van beperkt nut, omdat OSError: [Errno 17] File exists, i.e. errno.EEXIST, wordt opgehaald voor zowel bestanden als mappen. Het is betrouwbaarder om te controleren of de map bestaat.

Alternatief:

mkpath maakt de geneste map en doet niets als de map al bestaat. Dit werkt zowel in Python 2 als in 3.

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

Per Bug 10948, een ernstige beperking van dit alternatief is dat het maar één keer per python-proces werkt voor een bepaald pad. Met andere woorden, als u het gebruikt om een ​​map aan te maken, verwijder dan de map binnen of buiten Python en gebruik vervolgens mkpath om opnieuw dezelfde directory te creëren, mkpath gebruikt gewoon de ongeldige in de cache opgeslagen info om de map eerder te hebben gemaakt en zal de map niet opnieuw maken. In tegenstelling tot, os.makedirs vertrouwt niet op een dergelijke cache. Deze beperking is mogelijk goed voor sommige toepassingen.


Met betrekking tot de directory's mode, raadpleeg de documentatie als u er iets om geeft.


809
2018-01-16 17:31



Met behulp van try behalve en de juiste foutcode van errno module verlost van de race-conditie en is cross-platform:

import os
import errno

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

Met andere woorden, we proberen de mappen aan te maken, maar als ze al bestaan, negeren we de fout. Aan de andere kant wordt een andere fout gerapporteerd. Als u bijvoorbeeld dir 'a' van tevoren maakt en alle machtigingen ervan verwijdert, krijgt u een OSError opgevoed met errno.EACCES (Toestemming geweigerd, fout 13).


572
2018-02-17 17:17



Ik zou persoonlijk aanbevelen dat je gebruikt os.path.isdir() testen in plaats van 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

Als je hebt:

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

En een dwaze gebruikersinvoer:

:: /tmp/dirname/filename.etc

... Je zult eindigen met een map met de naam filename.etc wanneer je dat argument doorgeeft aan os.makedirs() als je test met os.path.exists().


85
2018-01-14 17:57



Controleren os.makedirs: (Het zorgt ervoor dat het volledige pad bestaat.)
 Om het feit dat de map mogelijk bestaat te verwerken, vang OSError. (Als exists_ok False is (de standaardinstelling), wordt een OSError opgehaald als de doelmap al bestaat.)

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

56
2017-11-07 19:01



Inzicht in de bijzonderheden van deze situatie

U geeft een bepaald bestand op een bepaald pad en u haalt de map uit het bestandspad. Nadat u hebt gecontroleerd of u de map hebt, probeert u vervolgens een bestand te openen om te lezen. Om op deze code te reageren:

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

We willen voorkomen dat de ingebouwde functie wordt overschreven, dir. Ook, filepath of misschien fullfilepath is waarschijnlijk een betere semantische naam dan filename dus dit zou beter zijn geschreven:

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

Uw einddoel is om dit bestand te openen, zegt u in eerste instantie, om te schrijven, maar u nadert in wezen dit doel (op basis van uw code), waardoor het bestand voor lezing:

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

Veronderstellend opening voor lezing

Waarom zou je een map maken voor een bestand waarvan je verwacht dat het er is en dat het kan lezen?

Probeer gewoon het bestand te openen.

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

Als de map of het bestand er niet is, krijg je een IOError met een bijbehorend foutnummer: errno.ENOENT zal naar het juiste foutnummer wijzen, ongeacht uw platform. Je kunt het vangen als je wilt, bijvoorbeeld:

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

Ervan uitgaande dat we openen voor schrijven

Dit is waarschijnlijk wat je wilt.

In dit geval hebben we waarschijnlijk geen raceomstandigheden. Dus doe gewoon wat je was, maar merk op dat je voor schrijven moet openen met de w modus (of a toevoegen). Het is ook een best practice van Python om de contextbeheerder te gebruiken voor het openen van bestanden.

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

Stel dat we verschillende Python-processen hebben die proberen al hun gegevens in dezelfde map te plaatsen. Dan hebben we misschien ruzie over het maken van de directory. In dat geval is het het beste om de makedirs oproep in een try-except blok.

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



Beginnend met Python 3.5, pathlib.Path.mkdir heeft een exist_ok vlag:

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

Hiermee wordt de map recursief gemaakt en wordt er geen uitzondering gegenereerd als de map al bestaat.

(net als os.makedirs heb een exists_ok vlag beginnend met python 3.2).


28
2017-12-14 16:06