Recentemente ho provato a fare qualcosa di simile e ho trovato queste risposte inadeguate per i miei casi d'uso (una libreria distribuita che deve rilevare la radice del progetto). Principalmente ho combattuto diversi ambienti e piattaforme e non ho ancora trovato qualcosa di perfettamente universale.
Codice locale al progetto
Ho visto questo esempio menzionato e utilizzato in alcuni posti, Django, ecc.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Per quanto semplice, funziona solo quando il file in cui si trova lo snippet fa effettivamente parte del progetto. Non recuperiamo la directory del progetto, ma la directory dello snippet
Allo stesso modo, l' approccio sys.modules si interrompe quando viene chiamato dall'esterno del punto di ingresso dell'applicazione, in particolare ho osservato che un thread figlio non può determinarlo senza relazione con il modulo " principale ". Ho inserito esplicitamente l'importazione in una funzione per dimostrare un'importazione da un thread figlio, spostandola al livello superiore di app.py lo risolverebbe.
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'esecuzione di questo programma produce un errore di attributo:
>>> 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__'
... quindi una soluzione basata sul threading
Posizione indipendente
Utilizzando la stessa struttura dell'applicazione di prima ma modificando 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))
Rompendo questo: per prima cosa vogliamo trovare con precisione l'ID thread del thread principale. In Python3.4 + la libreria di threading ha threading.main_thread()tuttavia, tutti non usano 3.4+ quindi cerchiamo in tutti i thread cercando il thread principale salvando il suo ID. Se il thread principale è già terminato, non verrà elencato nel file threading.enumerate(). Solleviamo una RuntimeError()in questo caso finché non trovo una soluzione migliore.
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")
Successivamente troviamo il primo stack frame del thread principale. Usando la funzione specifica di cPython sys._current_frames() otteniamo un dizionario dello stack frame corrente di ogni thread. Quindi utilizzando inspect.getouterframes()possiamo recuperare l'intero stack per il thread principale e il primissimo frame. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Infine, le differenze tra le implementazioni Windows e Linux inspect.getouterframes()devono essere gestite. Utilizzando il nome del file ripulito os.path.abspath()e os.path.dirname()ripulire le cose.
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))
Finora l'ho testato su Python2.7 e 3.6 su Windows e su Python3.4 su WSL
<ROOT>/__init__.pyesiste?