Недавно я пытался сделать что-то подобное, и я нашел эти ответы неадекватными для моих вариантов использования (распределенная библиотека, которая должна определять корень проекта). В основном я боролся с разными средами и платформами, но до сих пор не нашел чего-то совершенно универсального.
Используйте локальный код для проекта
Я видел, как этот пример упоминался и использовался в нескольких местах, в Django и т. Д.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Каким бы простым это ни было, он работает только тогда, когда файл, в котором находится фрагмент, фактически является частью проекта. Мы получаем не каталог проекта, а каталог сниппета.
Точно так же подход sys.modules не работает при вызове извне точки входа приложения, в частности, я заметил, что дочерний поток не может определить это без обратной связи с « основным » модулем. Я явно поместил импорт внутри функции, чтобы продемонстрировать импорт из дочернего потока, перемещение его на верхний уровень app.py исправит это.
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
При запуске этой программы возникает ошибка атрибута:
>>> 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__'
... следовательно, решение на основе потоков
Независимо от местоположения
Использование той же структуры приложения, что и раньше, но изменение 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))
Разбивка на части: сначала мы хотим точно найти идентификатор основного потока. В Python3.4 + библиотека потоковой передачи, threading.main_thread()однако, не использует версии 3.4+, поэтому мы ищем все потоки в поисках основного потока, сохраняя его идентификатор. Если основной поток уже завершился, он не будет указан в списке threading.enumerate(). В RuntimeError()этом случае мы поднимаем a, пока не найду лучшее решение.
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")
Затем мы находим самый первый кадр стека основного потока. Используя специальную функцию cPython, sys._current_frames() мы получаем словарь текущего кадра стека каждого потока. Затем, используя, inspect.getouterframes()мы можем получить весь стек для основного потока и самого первого кадра. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Наконец, inspect.getouterframes()необходимо учесть различия между реализациями Windows и Linux . Использование очищенного имени файла os.path.abspath()и os.path.dirname()очистка.
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))
До сих пор я тестировал это на Python2.7 и 3.6 в Windows, а также на Python3.4 на WSL.
<ROOT>/__init__.pyсуществует?