Comment obtenir le chemin du fichier exécuté en cours en Python?

193

Cela peut sembler une question pour les débutants, mais ce n'est pas le cas. Certaines approches courantes ne fonctionnent pas dans tous les cas:

sys.argv [0]

Cela signifie utiliser path = os.path.abspath(os.path.dirname(sys.argv[0])), mais cela ne fonctionne pas si vous exécutez à partir d'un autre script Python dans un autre répertoire, et cela peut arriver dans la vraie vie.

__fichier__

Cela signifie utiliser path = os.path.abspath(os.path.dirname(__file__)), mais j'ai trouvé que cela ne fonctionne pas:

  • py2exen'a pas d' __file__attribut, mais il existe une solution de contournement
  • Lorsque vous exécutez à partir de IDLE execute()sans __file__attribut
  • OS X 10.6 où j'obtiens NameError: global name '__file__' is not defined

Questions connexes avec des réponses incomplètes:

Je recherche une solution générique , qui fonctionnerait dans tous les cas d'utilisation ci-dessus.

Mettre à jour

Voici le résultat d'un testcase:

Sortie de python a.py (sous Windows)

a.py: __file__= a.py
a.py: os.getcwd()= C:\zzz

b.py: sys.argv[0]= a.py
b.py: __file__= a.py
b.py: os.getcwd()= C:\zzz

a.py

#! /usr/bin/env python
import os, sys

print "a.py: sys.argv[0]=", sys.argv[0]
print "a.py: __file__=", __file__
print "a.py: os.getcwd()=", os.getcwd()
print

execfile("subdir/b.py")

subdir / b.py

#! /usr/bin/env python
import os, sys

print "b.py: sys.argv[0]=", sys.argv[0]
print "b.py: __file__=", __file__
print "b.py: os.getcwd()=", os.getcwd()
print

arbre

C:.
|   a.py
\---subdir
        b.py
Sorin
la source

Réponses:

80

Vous ne pouvez pas déterminer directement l'emplacement du script principal en cours d'exécution. Après tout, parfois, le script ne provenait pas du tout d'un fichier. Par exemple, il peut provenir de l'interpréteur interactif ou d'un code généré dynamiquement stocké uniquement en mémoire.

Cependant, vous pouvez déterminer de manière fiable l'emplacement d'un module, car les modules sont toujours chargés à partir d'un fichier. Si vous créez un module avec le code suivant et le placez dans le même répertoire que votre script principal, le script principal peut importer le module et l'utiliser pour se localiser.

some_path / module_locator.py:

def we_are_frozen():
    # All of the modules are built-in to the interpreter, e.g., by py2exe
    return hasattr(sys, "frozen")

def module_path():
    encoding = sys.getfilesystemencoding()
    if we_are_frozen():
        return os.path.dirname(unicode(sys.executable, encoding))
    return os.path.dirname(unicode(__file__, encoding))

some_path / main.py:

import module_locator
my_path = module_locator.module_path()

Si vous avez plusieurs scripts principaux dans différents répertoires, vous aurez peut-être besoin de plus d'une copie de module_locator.

Bien sûr, si votre script principal est chargé par un autre outil qui ne vous permet pas d'importer des modules co-localisés avec votre script, alors vous n'avez pas de chance. Dans de tels cas, les informations que vous recherchez n'existent tout simplement nulle part dans votre programme. Votre meilleur pari serait de déposer un bug auprès des auteurs de l'outil.

Daniel Stutzbach
la source
2
Je mentionne que sur OS 10.6 Je reçois en NameError: global name '__file__' is not definedutilisant le fichier et ce n'est pas à l' intérieur du IDLE. Pensez que ce __file__n'est défini qu'à l'intérieur des modules.
sorin
1
@Sorin Sbarnea: J'ai mis à jour ma réponse avec la façon dont je contournais cela.
Daniel Stutzbach
2
Merci, mais en fait, le problème du manque __file__n'avait rien à voir avec Unicode. Je ne sais pas pourquoi __file__n'est pas défini mais je recherche une solution générique qui fonctionnera dans tous les cas.
sorin
1
Désolé, ce n'est pas possible dans tous les cas. Par exemple, j'essaie de faire cela dans waf.googlecode.com à partir d'un fichier wscript (python). Ces fichiers sont exécutés mais ce ne sont pas des modules et vous ne pouvez pas en faire des modules (ils peuvent être n'importe quel sous-répertoire de l'arborescence des sources).
sorin
1
Cela ne vous donnera-t-il pas l'emplacement de la some_path/module_locator.pyplace?
Casebash
68

Tout d'abord, vous devez importer depuis inspectetos

from inspect import getsourcefile
from os.path import abspath

Ensuite, où que vous vouliez trouver le fichier source, utilisez simplement

abspath(getsourcefile(lambda:0))
ArtOfWarfare
la source
Meilleure réponse. Je vous remercie.
Devan Williams
4
Très bonne réponse. Merci. Cela semble également être la réponse la plus courte et la plus portable (fonctionnant sur différents systèmes d'exploitation) et ne rencontre pas de problèmes tels que NameError: global name '__file__' is not defined(une autre solution a causé cela).
Edward
Une autre possibilité qui est tout aussi courte: lambda:_. Cela a fonctionné pour moi - je ne suis pas sûr que cela fonctionnera toujours. lambda:0fonctionne probablement plus vite d'une quantité incommensurablement petite (ou peut-être pas si petite ... pourrait être une charge immédiate ou quelque chose d'encore plus rapide pour 0une charge globale pour _?) On peut se demander si c'est plus propre ou plus facile à lire ou même plus intelligent / obscur.
ArtOfWarfare
Comme pour presque toutes les propositions que j'ai essayées, cela renvoie simplement le cwd pour moi, pas le fichier de répertoire que j'exécute (débogage).
James
1
@James - Ce code va dans le fichier que vous exécutez ... l'exécution getsourcefile(lambda:0)n'aura aucun sens et reviendra simplement Nonesi vous essayez de l'exécuter à une invite interactive (puisque le lambdane sera dans aucun fichier source.) Si vous voulez savoir d'où vient une autre fonction ou un autre objet dans un environnement interactif, abspath(getsourcefile(thatFunctionOrObject))sera peut -être plus utile pour vous?
ArtOfWarfare
16

cette solution est robuste même dans les exécutables

import inspect, os.path

filename = inspect.getframeinfo(inspect.currentframe()).filename
path     = os.path.dirname(os.path.abspath(filename))
José Crespo Barrios
la source
2
Cela devrait être la bonne réponse. Cela fonctionne même dans entry_point: console_script, mais aucune des autres réponses.
Polv
15

Je rencontrais un problème similaire, et je pense que cela pourrait résoudre le problème:

def module_path(local_function):
   ''' returns the module path without the use of __file__.  Requires a function defined
   locally in the module.
   from http://stackoverflow.com/questions/729583/getting-file-path-of-imported-module'''
   return os.path.abspath(inspect.getsourcefile(local_function))

Cela fonctionne pour les scripts réguliers et en veille. Tout ce que je peux dire, c'est de l'essayer pour les autres!

Mon utilisation typique:

from toolbox import module_path
def main():
   pass # Do stuff

global __modpath__
__modpath__ = module_path(main)

Maintenant, j'utilise __modpath__ au lieu de __file__.

Garrett Berg
la source
2
Selon le guide de style de codage PEP8 , il ne faut jamais créer de noms avec des traits de soulignement doubles en début et en fin - ils __modpath__doivent donc être renommés. Vous n'avez probablement pas non plus besoin de la globaldéclaration. Sinon +1!
martineau
4
En fait, vous pouvez définir la fonction locale directement dans l'appel à module_path(). ie module_path(lambda _: None)qui ne dépend pas des autres contenus du script dans
lequel
@martineau: J'ai pris votre suggestion lambda _: Noneet je l'ai utilisée pendant près de deux ans, mais tout à l'heure j'ai découvert que je pouvais la condenser à juste lambda:0. Y a-t-il une raison particulière pour laquelle vous avez suggéré la forme que vous avez faite, avec un argument ignoré de _, plutôt que sans argument du tout? Y a-t-il quelque chose de supérieur Nonepréfixé avec un espace plutôt que juste 0? Ils sont tous les deux tout aussi énigmatiques, je pense, un seul compte 8 caractères tandis que l'autre compte 14 caractères.
ArtOfWarfare
@ArtOfWarfare: La différence entre les deux est que lambda _:c'est une fonction qui prend un argument et lambda:qui n'en prend pas. Cela n'a pas d'importance puisque la fonction n'est jamais appelée. De même, la valeur de retour utilisée n'a pas d'importance. J'imagine que j'ai choisi Noneparce qu'à l'époque, cela semblait indiquer que c'était une fonction de ne rien faire et de ne jamais être mieux appelée. L'espace devant celui-ci est facultatif et là encore uniquement pour une meilleure lisibilité (toujours essayer de suivre PEP8 est une habitude).
martineau
@martineau: C'est évidemment un abus de lambdacependant, l'utiliser pour quelque chose qu'il n'a jamais été censé faire. Si vous deviez suivre PEP8, je pense que le bon contenu serait passnon None, mais ce n'est pas valide de mettre une déclaration dans un lambda, donc vous devez mettre quelque chose avec une valeur. Il y a quelques éléments valides à 2 caractères que vous pouvez insérer, mais je pense que les seuls éléments valides à un seul caractère que vous pouvez insérer sont 0-9 (ou un nom de variable à caractère unique attribué en dehors du lambda.) Je pense que le 0mieux indique le néant de 0- 9.
ArtOfWarfare
6

La réponse courte est qu'il n'y a pas de moyen garanti d'obtenir les informations souhaitées , mais il existe des heuristiques qui fonctionnent presque toujours dans la pratique. Vous pourriez regarder Comment puis-je trouver l'emplacement de l'exécutable en C? . Il aborde le problème d'un point de vue C, mais les solutions proposées sont facilement transcrites en Python.

Dale Hagglund
la source
Bonjour, mon drapeau a été refusé donc j'ai maintenant commencé une discussion sur meta: meta.stackoverflow.com/questions/277272/…
ArtOfWarfare
Cependant, il existe déjà des réponses de travail simples sur cette page. Ma solution fonctionne bien: stackoverflow.com/a/33531619/3787376 .
Edward
5

Voir ma réponse à la question Importer des modules à partir du dossier parent pour des informations connexes, y compris pourquoi ma réponse n'utilise pas la __file__variable non fiable . Cette solution simple doit être compatible avec différents systèmes d'exploitation comme les modules oset inspectfaire partie de Python.

Tout d' abord, vous devez parties à l'importation de l' inspectent et os modules.

from inspect import getsourcefile
from os.path import abspath

Ensuite, utilisez la ligne suivante partout ailleurs dans votre code Python:

abspath(getsourcefile(lambda:0))

Comment ça fonctionne:

Depuis le module intégré os(description ci-dessous), l' abspathoutil est importé.

Routines du système d'exploitation pour Mac, NT ou Posix selon le système sur lequel nous sommes.

Ensuite getsourcefile(description ci-dessous) est importé du module intégré inspect.

Obtenez des informations utiles à partir d'objets Python en direct.

  • abspath(path) renvoie la version absolue / complète d'un chemin de fichier
  • getsourcefile(lambda:0)obtient en quelque sorte le fichier source interne de l'objet de la fonction lambda, donc retourne '<pyshell#nn>'dans le shell Python ou retourne le chemin du fichier du code Python en cours d'exécution.

En utilisant abspathle résultat de, getsourcefile(lambda:0)vous devez vous assurer que le chemin du fichier généré est le chemin complet du fichier Python.
Cette solution expliquée était à l'origine basée sur le code de la réponse à Comment obtenir le chemin du fichier actuellement exécuté en Python? .

Edward
la source
oui, je viens de trouver la même solution ... bien meilleure que les réponses qui disent simplement que cela ne peut pas être fait de manière fiable ... à moins que la question ne demande pas ce que je pense que c'est ...
Grady Player
5

Vous avez simplement appelé:

path = os.path.abspath(os.path.dirname(sys.argv[0]))

au lieu de:

path = os.path.dirname(os.path.abspath(sys.argv[0]))

abspath()vous donne le chemin absolu de sys.argv[0](le nom de fichier dans lequel se trouve votre code) et dirname()renvoie le chemin du répertoire sans le nom de fichier.

dgb
la source
4

Cela devrait faire l'affaire de manière multiplateforme (tant que vous n'utilisez pas l'interpréteur ou quelque chose):

import os, sys
non_symbolic=os.path.realpath(sys.argv[0])
program_filepath=os.path.join(sys.path[0], os.path.basename(non_symbolic))

sys.path[0]est le répertoire dans lequel se trouve votre script appelant (le premier endroit où il recherche les modules à utiliser par ce script). Nous pouvons prendre le nom du fichier lui-même à la fin de sys.argv[0](ce que j'ai fait avec os.path.basename). os.path.joinles colle simplement ensemble de manière multiplateforme. os.path.realpaths'assure simplement que si nous obtenons des liens symboliques avec des noms différents de ceux du script lui-même, nous obtenons toujours le vrai nom du script.

Je n'ai pas de Mac; donc, je ne l'ai pas testé sur un seul. S'il vous plaît laissez-moi savoir si cela fonctionne, comme il semble que cela devrait. J'ai testé cela sous Linux (Xubuntu) avec Python 3.4. Notez que de nombreuses solutions à ce problème ne fonctionnent pas sur les Mac (depuis que j'ai entendu dire que ce __file__n'est pas présent sur les Mac).

Notez que si votre script est un lien symbolique, il vous donnera le chemin du fichier vers lequel il est lié (et non le chemin du lien symbolique).

Brōtsyorfuzthrāx
la source
2

Vous pouvez utiliser à Pathpartir du pathlibmodule:

from pathlib import Path

# ...

Path(__file__)

Vous pouvez utiliser l'appel à parentpour aller plus loin dans le chemin:

Path(__file__).parent
Gavriel Cohen
la source
Cette réponse utilise la __file__variable qui peut ne pas être fiable (n'est pas toujours le chemin complet du fichier, ne fonctionne pas sur tous les systèmes d'exploitation, etc.) comme les utilisateurs de StackOverflow l'ont souvent mentionné. Changer la réponse pour ne pas l'inclure causera moins de problèmes et sera plus compatible entre les deux. Pour plus d'informations, consultez stackoverflow.com/a/33532002/3787376 .
Edward
@ mrroot5 Ok, alors supprimez votre commentaire s'il vous plaît.
Gavriel Cohen
1

Si le code provient d'un fichier, vous pouvez obtenir son nom complet

sys._getframe().f_code.co_filename

Vous pouvez également récupérer le nom de la fonction sous la forme f_code.co_name

Alex Cohn
la source
0

Ajoutez simplement ce qui suit:

from sys import *
path_to_current_file = sys.argv[0]
print(path_to_current_file)

Ou:

from sys import *
print(sys.argv[0])
H. Kamran
la source
0

Ma solution est:

import os
print(os.path.dirname(os.path.abspath(__file__)))
ThienSuBS
la source
-1
import os
current_file_path=os.path.dirname(os.path.realpath('__file__'))
Mohamed.Abdo
la source
Cela n'a pas de sens. Premièrement, '__file__'ne devrait pas être une chaîne, deuxièmement, si vous le faisiez __file__, cela ne fonctionnerait que pour le fichier dans lequel se trouve cette ligne de code, et non pour le fichier qui est exécuté?
Andreas Storvik Strauman