Comment ouvrir de manière fiable un fichier dans le même répertoire qu'un script Python

157

J'avais l'habitude d'ouvrir des fichiers qui se trouvaient dans le même répertoire que le script Python en cours d'exécution en utilisant simplement une commande comme

open("Some file.txt", "r")

Cependant, j'ai découvert que lorsque le script était exécuté sous Windows en double-cliquant dessus, il tentait d'ouvrir le fichier à partir du mauvais répertoire.

Depuis, j'ai utilisé une commande du formulaire

open(os.path.join(sys.path[0], "Some file.txt"), "r")

chaque fois que je voulais ouvrir un fichier. Cela fonctionne pour mon utilisation particulière, mais je ne sais pas si cela sys.path[0]pourrait échouer dans un autre cas d'utilisation.

Ma question est donc la suivante: quel est le moyen le meilleur et le plus fiable d'ouvrir un fichier qui se trouve dans le même répertoire que le script Python en cours d'exécution?

Voici ce que j'ai pu comprendre jusqu'à présent:

  • os.getcwd()et os.path.abspath('')renvoie le "répertoire de travail actuel", pas le répertoire de script.

  • os.path.dirname(sys.argv[0])et os.path.dirname(__file__)renvoyer le chemin utilisé pour appeler le script, qui peut être relatif ou même vide (si le script est dans le cwd). De plus, __file__n'existe pas lorsque le script est exécuté en IDLE ou PythonWin.

  • sys.path[0]et os.path.abspath(os.path.dirname(sys.argv[0]))semblent renvoyer le répertoire de script. Je ne sais pas s'il y a une différence entre ces deux.

Éditer:

Je viens de réaliser que ce que je veux faire serait mieux décrit comme "ouvrir un fichier dans le même répertoire que le module contenant". En d'autres termes, si j'importe un module que j'ai écrit dans un autre répertoire, et que ce module ouvre un fichier, je veux qu'il recherche le fichier dans le répertoire du module. Je ne pense pas que ce que j'ai trouvé soit capable de faire ça ...

dln385
la source

Réponses:

199

J'utilise toujours:

__location__ = os.path.realpath(
    os.path.join(os.getcwd(), os.path.dirname(__file__)))

L' join()appel précède le répertoire de travail actuel, mais la documentation indique que si un chemin est absolu, tous les autres chemins qui en restent sont supprimés. Par conséquent, getcwd()est supprimé lorsque dirname(__file__)renvoie un chemin absolu.

En outre, l' realpathappel résout les liens symboliques le cas échéant. Cela évite les problèmes lors du déploiement avec setuptools sur les systèmes Linux (les scripts sont liés par un lien symbolique /usr/bin/- au moins sur Debian).

Vous pouvez utiliser ce qui suit pour ouvrir des fichiers dans le même dossier:

f = open(os.path.join(__location__, 'bundled-resource.jpg'));
# ...

J'utilise ceci pour regrouper des ressources avec plusieurs applications Django sur Windows et Linux et cela fonctionne comme un charme!

André Caron
la source
4
S'il __file__ne peut pas être utilisé, utilisez à la sys.argv[0]place de dirname(__file__). Le reste devrait fonctionner comme prévu. J'aime utiliser __file__parce que dans le code de la bibliothèque, il se sys.argv[0]peut que le code ne pointe pas du tout vers votre code, surtout s'il est importé via un script tiers.
André Caron
1
Le problème avec ceci est que cela variera si le fichier en cours d'exécution provient directement de l'interrupteur ou s'il est importé. Voir ma réponse pour les différences entre file et sys.argv [0]
Zimm3r
Est-il donc correct de dire que la variation décrite dans la réponse de Zimm3r est adressée en utilisant realpath( join( getcwd(), dirname(__file__) ))comme décrit ici?
pianoJames
44

Pour citer la documentation Python:

Comme initialisé au démarrage du programme, le premier élément de cette liste, chemin [0], est le répertoire contenant le script qui a été utilisé pour appeler l'interpréteur Python. Si le répertoire du script n'est pas disponible (par exemple, si l'interpréteur est appelé de manière interactive ou si le script est lu à partir de l'entrée standard), chemin [0] est la chaîne vide, qui dirige Python vers les modules de recherche dans le répertoire courant en premier. Notez que le répertoire de script est inséré avant les entrées insérées à la suite de PYTHONPATH.

sys.path [0] est ce que vous recherchez.

SINGE ROUGE
la source
10
Et pour le chemin complet du fichier: os.path.join(sys.path[0], 'some file.txt'). Cela devrait gérer correctement les espaces et les barres obliques sur tous les systèmes.
Jacktose
Cette réponse à la première question, pas celle après l'EDIT.
mcoolive
22

Ok voici ce que je fais

sys.argv est toujours ce que vous tapez dans le terminal ou utilisez comme chemin de fichier lors de son exécution avec python.exe ou pythonw.exe

Par exemple, vous pouvez exécuter le fichier text.py de plusieurs façons, elles vous donnent chacune une réponse différente, elles vous donnent toujours le chemin d'accès que python a été tapé.

    C:\Documents and Settings\Admin>python test.py
    sys.argv[0]: test.py
    C:\Documents and Settings\Admin>python "C:\Documents and Settings\Admin\test.py"
    sys.argv[0]: C:\Documents and Settings\Admin\test.py

Ok, sachez que vous pouvez obtenir le nom du fichier, très bien, maintenant pour obtenir le répertoire de l'application, vous pouvez utiliser os.path, en particulier abspath et dirname

    import sys, os
    print os.path.dirname(os.path.abspath(sys.argv[0]))

Cela produira ceci:

   C:\Documents and Settings\Admin\

il affichera toujours ceci, peu importe si vous tapez python test.py ou python "C: \ Documents and Settings \ Admin \ test.py"

Le problème avec l'utilisation de __file__ Considérez ces deux fichiers test.py

import sys
import os

def paths():
        print "__file__: %s" % __file__
        print "sys.argv: %s" % sys.argv[0]

        a_f = os.path.abspath(__file__)
        a_s = os.path.abspath(sys.argv[0])

        print "abs __file__: %s" % a_f
        print "abs sys.argv: %s" % a_s

if __name__ == "__main__":
    paths()

import_test.py

import test
import sys

test.paths()

print "--------"
print __file__
print sys.argv[0]

Sortie de "python test.py"

C:\Documents and Settings\Admin>python test.py
__file__: test.py
sys.argv: test.py
abs __file__: C:\Documents and Settings\Admin\test.py
abs sys.argv: C:\Documents and Settings\Admin\test.py

Sortie de "python test_import.py"

C:\Documents and Settings\Admin>python test_import.py
__file__: C:\Documents and Settings\Admin\test.pyc
sys.argv: test_import.py
abs __file__: C:\Documents and Settings\Admin\test.pyc
abs sys.argv: C:\Documents and Settings\Admin\test_import.py
--------
test_import.py
test_import.py

Ainsi, comme vous pouvez le voir, file vous donne toujours le fichier python à partir duquel il est exécuté, alors que sys.argv [0] vous donne toujours le fichier que vous avez exécuté depuis l'interpréteur. En fonction de vos besoins, vous devrez choisir celui qui correspond le mieux à vos besoins.

Zimm3r
la source
3
Ceci est une preuve élaborée que l'implémentation reflète la documentation. __file__est censé "toujours vous donner le chemin vers le fichier courant", et sys.argv[0]est censé "toujours donner le chemin du script qui a initié le processus". Dans tous les cas, l'utilisation __file__dans le script qui est appelé vous donne toujours des résultats précis.
André Caron
Si vous avez la référence __file__au niveau supérieur du script, cela fonctionnera comme prévu.
Matthew Schinckel
-1

J'ai pu utiliser le code fourni par dcolish avec succès car j'avais un problème similaire avec la lecture d'un fichier texte spécifique. Le fichier n'est pas dans le même cwd que le fichier Python.

Jaclyn Horton
la source
1
Veuillez ne pas ajouter «merci» comme réponse. Une fois que vous aurez une réputation suffisante , vous pourrez voter pour les questions et réponses que vous avez trouvées utiles. - De l'avis
Roberto Caboni
-3

Je le ferais de cette façon:

from os.path import abspath, exists

f_path = abspath("fooabar.txt")

if exists(f_path):
    with open(f_path) as f:
        print f.read()

Le code ci-dessus construit un chemin absolu vers le fichier en utilisant abspath et équivaut à utiliser normpath(join(os.getcwd(), path))[qui provient des pydocs]. Il vérifie ensuite si ce fichier existe réellement , puis utilise un gestionnaire de contexte pour l'ouvrir afin que vous n'ayez pas à vous rappeler d'appeler close sur le descripteur de fichier. IMHO, le faire de cette façon vous évitera beaucoup de douleur à long terme.

décolâtre
la source
Cela ne répond pas à la question de l'affiche. dln385 dit spécifiquement que os.path.abspathne résout pas les chemins d'accès aux fichiers dans le même dossier que le script si le script n'est pas dans le répertoire actuel.
André Caron
Ah! J'ai supposé que l'utilisateur exécutait ce script dans le même répertoire que le fichier qu'il voulait lire, PAS dans le répertoire de module de quelque chose dans son PYTHONPATH. Cela m'apprendra à faire des hypothèses ...
décolish
abspath ne fonctionnera pas car il est impossible pour le runtime python de rechercher sur le système de fichiers du système d'exploitation en utilisant une fonction comme celle-ci.
akshat thakar