J'ai récemment essayé de faire quelque chose de similaire et j'ai trouvé ces réponses inadéquates pour mes cas d'utilisation (une bibliothèque distribuée qui doit détecter la racine du projet). J'ai principalement combattu différents environnements et plates-formes, et je n'ai toujours pas trouvé quelque chose de parfaitement universel.
Code local au projet
J'ai vu cet exemple mentionné et utilisé dans quelques endroits, Django, etc.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Aussi simple que cela soit, cela ne fonctionne que lorsque le fichier dans lequel se trouve l'extrait de code fait réellement partie du projet. Nous ne récupérons pas le répertoire du projet, mais plutôt le répertoire de l'extrait de code
De même, l' approche sys.modules tombe en panne lorsqu'elle est appelée depuis l'extérieur du point d'entrée de l'application, en particulier, j'ai observé qu'un thread enfant ne peut pas le déterminer sans rapport avec le module « principal ». J'ai explicitement mis l'importation dans une fonction pour démontrer une importation à partir d'un thread enfant, le déplacer vers le niveau supérieur de app.py le corrigerait.
app/
|-- config
| `-- __init__.py
| `-- settings.py
`-- app.py
app.py
#!/usr/bin/env python
import threading
def background_setup():
# Explicitly importing this from the context of the child thread
from config import settings
print(settings.ROOT_DIR)
# Spawn a thread to background preparation tasks
t = threading.Thread(target=background_setup)
t.start()
# Do other things during initialization
t.join()
# Ready to take traffic
settings.py
import os
import sys
ROOT_DIR = None
def setup():
global ROOT_DIR
ROOT_DIR = os.path.dirname(sys.modules['__main__'].__file__)
# Do something slow
L'exécution de ce programme produit une erreur d'attribut:
>>> import main
>>> Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python2714\lib\threading.py", line 801, in __bootstrap_inner
self.run()
File "C:\Python2714\lib\threading.py", line 754, in run
self.__target(*self.__args, **self.__kwargs)
File "main.py", line 6, in background_setup
from config import settings
File "config\settings.py", line 34, in <module>
ROOT_DIR = get_root()
File "config\settings.py", line 31, in get_root
return os.path.dirname(sys.modules['__main__'].__file__)
AttributeError: 'module' object has no attribute '__file__'
... d'où une solution basée sur le threading
Indépendant de l'emplacement
Utilisation de la même structure d'application qu'avant mais modification de settings.py
import os
import sys
import inspect
import platform
import threading
ROOT_DIR = None
def setup():
main_id = None
for t in threading.enumerate():
if t.name == 'MainThread':
main_id = t.ident
break
if not main_id:
raise RuntimeError("Main thread exited before execution")
current_main_frame = sys._current_frames()[main_id]
base_frame = inspect.getouterframes(current_main_frame)[-1]
if platform.system() == 'Windows':
filename = base_frame.filename
else:
filename = base_frame[0].f_code.co_filename
global ROOT_DIR
ROOT_DIR = os.path.dirname(os.path.abspath(filename))
Décomposer ceci: Nous voulons d'abord trouver avec précision l'ID de thread du thread principal. Dans Python3.4 +, la bibliothèque de threads a threading.main_thread()cependant, tout le monde n'utilise pas 3.4+ donc nous recherchons dans tous les threads à la recherche du thread principal, sauf son ID. Si le thread principal est déjà terminé, il ne sera pas répertorié dans le threading.enumerate(). Nous soulevons un RuntimeError()dans ce cas jusqu'à ce que je trouve une meilleure solution.
main_id = None
for t in threading.enumerate():
if t.name == 'MainThread':
main_id = t.ident
break
if not main_id:
raise RuntimeError("Main thread exited before execution")
Ensuite, nous trouvons le tout premier cadre de pile du thread principal. En utilisant la fonction spécifique cPython, sys._current_frames() nous obtenons un dictionnaire de la trame de pile actuelle de chaque thread. Ensuite, en utilisant, inspect.getouterframes()nous pouvons récupérer la pile entière pour le thread principal et la toute première image. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Enfin, les différences entre les implémentations Windows et Linux inspect.getouterframes()doivent être gérées. Utilisez le nom de fichier nettoyé os.path.abspath()et os.path.dirname()nettoyez les choses.
if platform.system() == 'Windows':
filename = base_frame.filename
else:
filename = base_frame[0].f_code.co_filename
global ROOT_DIR
ROOT_DIR = os.path.dirname(os.path.abspath(filename))
Jusqu'à présent, j'ai testé cela sur Python2.7 et 3.6 sur Windows ainsi que sur Python3.4 sur WSL
<ROOT>/__init__.pyn'existe?