Ostatnio próbowałem zrobić coś podobnego i stwierdziłem, że te odpowiedzi nie są odpowiednie dla moich przypadków użycia (rozproszonej biblioteki, która musi wykryć root projektu). Przede wszystkim walczyłem w różnych środowiskach i platformach i nadal nie znalazłem czegoś idealnie uniwersalnego.
Kod lokalny dla projektu
Widziałem ten przykład wspomniany i używany w kilku miejscach, Django itp.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Jest to proste, działa tylko wtedy, gdy plik, w którym znajduje się fragment kodu, jest faktycznie częścią projektu. Nie pobieramy katalogu projektu, ale zamiast tego katalog fragmentu
Podobnie, podejście sys.modules psuje się, gdy jest wywoływane spoza punktu wejścia aplikacji, w szczególności zauważyłem, że wątek potomny nie może tego określić bez odniesienia do modułu „ głównego ”. Jawnie umieściłem import wewnątrz funkcji, aby zademonstrować import z wątku potomnego, przeniesienie go na najwyższy poziom app.py naprawiłoby to.
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
Uruchomienie tego programu powoduje błąd atrybutu:
>>> 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__'
... stąd rozwiązanie oparte na wątkach
Lokalizacja niezależna
Używając tej samej struktury aplikacji co poprzednio, ale modyfikując 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))
Podsumowanie: Najpierw chcemy dokładnie znaleźć identyfikator wątku głównego wątku. Jednak w Pythonie 3.4 + biblioteka wątków nie threading.main_thread()wszyscy używają wersji 3.4+, więc przeszukujemy wszystkie wątki szukając głównego wątku, zachowując jego identyfikator. Jeśli główny wątek został już zamknięty, nie będzie wymieniony w threading.enumerate(). Podnosimy RuntimeError()w tej sprawie, dopóki nie znajdę lepszego rozwiązania.
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")
Następnie znajdujemy pierwszą ramkę stosu głównego wątku. Używając funkcji specyficznej dla cPythona sys._current_frames() , otrzymujemy słownik bieżącej ramki stosu każdego wątku. Następnie wykorzystując inspect.getouterframes()możemy pobrać cały stos dla głównego wątku i pierwszej klatki. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Wreszcie, należy zająć się różnicami między implementacjami systemów Windows i Linux inspect.getouterframes(). Używając oczyszczonej nazwy pliku os.path.abspath()i os.path.dirname()wyczyść wszystko.
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))
Do tej pory testowałem to na Pythonie2.7 i 3.6 w systemie Windows oraz Python3.4 na WSL
<ROOT>/__init__.pyistnieje?