Je voudrais voir quel est le meilleur moyen de déterminer le répertoire de script actuel en python?
J'ai découvert qu'en raison des nombreuses façons d'appeler du code python, il est difficile de trouver une bonne solution.
Voici quelques problèmes:
__file__
n'est pas défini si le script est exécuté avec exec
, execfile
__module__
est défini uniquement dans les modulesCas d'utilisation:
./myfile.py
python myfile.py
./somedir/myfile.py
python somedir/myfile.py
execfile('myfile.py')
(à partir d'un autre script, pouvant être situé dans un autre répertoire et pouvant avoir un autre répertoire en cours.Je sais qu'il n'y a pas de solution parfaite, mais je cherche la meilleure approche pour résoudre la plupart des cas.
L'approche la plus utilisée est os.path.dirname(os.path.abspath(__file__))
, mais cela ne fonctionne vraiment pas si vous exécutez le script depuis un autre script avec exec()
.
Toute solution utilisant le répertoire en cours échouera. Cela peut être différent en fonction de la façon dont le script est appelé ou peut être modifié dans le script en cours d'exécution.
os.path.dirname(os.path.abspath(__file__))
est en effet le meilleur que vous obtiendrez.
Il est inhabituel d'exécuter un script avec exec
/execfile
; normalement, vous devriez utiliser l'infrastructure du module pour charger les scripts. Si vous devez utiliser ces méthodes, je vous suggère de définir __file__
dans la globals
que vous transmettez au script afin qu'il puisse lire ce nom de fichier.
Il n’ya pas d’autre moyen d’obtenir le nom de fichier dans un code exécuté: vous remarquerez peut-être que le fichier CWD se trouve à un endroit complètement différent.
Si vous voulez vraiment couvrir le cas où un script est appelé via execfile(...)
, vous pouvez utiliser le module inspect
pour en déduire le nom du fichier (chemin compris). Autant que je sache, cela fonctionnera dans tous les cas que vous avez énumérés:
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
#!/usr/bin/env python
import inspect
import os
import sys
def get_script_dir(follow_symlinks=True):
if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
path = os.path.abspath(sys.executable)
else:
path = inspect.getabsfile(get_script_dir)
if follow_symlinks:
path = os.path.realpath(path)
return os.path.dirname(path)
print(get_script_dir())
Cela fonctionne sur CPython, Jython, Pypy. Cela fonctionne si le script est exécuté avec execfile()
(les solutions basées sur sys.argv[0]
et __file__
échoueraient ici). Cela fonctionne si le script est à l'intérieur n fichier Zip exécutable (/ un oeuf) . Cela fonctionne si le script est "importé" (PYTHONPATH=/path/to/library.Zip python -mscript_to_run
) depuis un fichier Zip; dans ce cas, il renvoie le chemin de l'archive. Cela fonctionne si le script est compilé dans un exécutable autonome (sys.frozen
). Cela fonctionne pour les liens symboliques (realpath
élimine les liens symboliques). Cela fonctionne dans un interprète interactif; Dans ce cas, il renvoie le répertoire de travail en cours.
Dans Python 3.4+, vous pouvez utiliser le module simple pathlib
:
from inspect import currentframe, getframeinfo
from pathlib import Path
filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent
L'approche os.path...
était la "chose faite" dans Python 2.
Dans Python 3, vous pouvez trouver le répertoire du script comme suit:
from pathlib import Path
cwd = Path(__file__).parents[0]
Il suffit d'utiliser os.path.dirname(os.path.abspath(__file__))
et d'examiner très attentivement si le cas où exec
est utilisé est vraiment nécessaire. Si vous ne pouvez pas utiliser votre script en tant que module, cela pourrait être un signe de problème.
Gardez à l'esprit Zen de Python # 8, et si vous croyez qu'il existe un bon argument en faveur d'un cas d'utilisation où cela doit fonctionner pour exec
, alors laissez Nous en savons plus sur l’arrière-plan du problème.
Aurait
import os
cwd = os.getcwd()
faites ce que vous voulez? Je ne suis pas sûr de ce que vous entendez exactement par "le répertoire de script actuel". Quelle serait la sortie attendue pour les cas d'utilisation que vous avez donnés?
import os
import sys
def get_script_path():
return os.path.dirname(os.path.realpath(sys.argv[0]))
my_script_dir = get_script_path()
print my_script_dir
Cela vous donne le répertoire du script en haut de la pile (c'est-à-dire celui en cours d'exécution - pas celui de Python, qui est généralement le premier exécuté, retournant C:/
)
Premièrement… quelques cas d'utilisation manquants ici si nous parlons de moyens d'injecter du code anonyme….
code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)
Mais la vraie question est la suivante: quel est votre objectif - essayez-vous d’imposer une sorte de sécurité? Ou êtes-vous simplement intéressé par ce qui est chargé?.
Si vous êtes intéressé par security , le nom du fichier importé via exec/execfile est sans importance - vous devez utiliser rexec , qui offre les avantages suivants:
Ce module contient la classe RExec, qui prend en charge les méthodes r_eval (), r_execfile (), r_exec () et r_import (), qui sont des versions restreintes des fonctions standard Python eval (), execfile () et les instructions exec et import. Le code exécuté dans cet environnement restreint n'aura accès qu'aux modules et fonctions considérés comme sûrs; vous pouvez sous-classer RExec ajouter ou supprimer des fonctionnalités à votre guise.
Cependant, s’il s’agit davantage d’une recherche académique, voici quelques approches loufoques que vous pourrez peut-être approfondir un peu plus.
Exemples de scripts:
./ deep.py
print ' >> level 1'
execfile('deeper.py')
print ' << level 1'
./ deeper.py
print '\t >> level 2'
exec("import sys; sys.path.append('/tmp'); import deepest")
print '\t << level 2'
/tmp/deepest.py
print '\t\t >> level 3'
print '\t\t\t I can see the earths core.'
print '\t\t << level 3'
./ codespy.py
import sys, os
def overseer(frame, event, arg):
print "loaded(%s)" % os.path.abspath(frame.f_code.co_filename)
sys.settrace(overseer)
execfile("deep.py")
sys.exit(0)
Sortie
loaded(/Users/synthesizerpatel/deep.py)
>> level 1
loaded(/Users/synthesizerpatel/deeper.py)
>> level 2
loaded(/Users/synthesizerpatel/<string>)
loaded(/tmp/deepest.py)
>> level 3
I can see the earths core.
<< level 3
<< level 2
<< level 1
Bien sûr, il s’agit d’une manière très consommatrice de ressources: vous traqueriez tout votre code. Pas très efficace. Mais, je pense que c’est une approche novatrice puisqu’elle continue de fonctionner même au plus profond de votre nid. Vous ne pouvez pas remplacer 'eval'. Bien que vous pouvez remplacer execfile ().
Notez que cette approche ne couvre que exec/execfile, pas "import". Pour un accroissement de charge 'module' de niveau supérieur, vous pourrez peut-être utiliser sys.path_hooks (Rédaction fournie par PyMOTW).
C'est tout ce que j'ai en tête.
Cela devrait fonctionner dans la plupart des cas:
import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
Voici une solution partielle, encore meilleure que toutes celles publiées jusqu'à présent.
import sys, os, os.path, inspect
#os.chdir("..")
if '__file__' not in locals():
__file__ = inspect.getframeinfo(inspect.currentframe())[0]
print os.path.dirname(os.path.abspath(__file__))
Maintenant, cela fonctionne avec tous les appels, mais si quelqu'un utilise chdir()
pour changer le répertoire actuel, cela échouera également.
Remarques:
sys.argv[0]
ne va pas fonctionner, retournera -c
si vous exécutez le script avec python -c "execfile('path-tester.py')"
Espérons que cela aide: - Si vous exécutez un script/module de n’importe où, vous pourrez accéder à la variable __file__
, qui est une variable de module représentant l’emplacement du script.
Par contre, si vous utilisez l’interprète, vous n’avez pas accès à cette variable. Vous obtiendrez un nom NameError
et os.getcwd()
vous donneront le mauvais répertoire si vous êtes exécuter le fichier quelque part ailleurs.
Cette solution devrait vous donner ce que vous cherchez dans tous les cas:
from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))
Je ne l'ai pas bien testé, mais cela a résolu mon problème.