Python - получить путь к корневой структуре проекта
У меня есть проект python с конфигурационным файлом в корне проекта.
Файл конфигурации должен быть доступен в нескольких файлах по всему проекту.
Итак, это выглядит примерно так: <ROOT>/configuration.conf
<ROOT>/A/a.py
, <ROOT>/A/B/b.py
(когда b, a.py обращается к файлу конфигурации).
Какой лучший/самый простой способ получить путь к корню проекта и конфигурационному файлу без зависимости от того, в каком файле находится внутри проекта? без использования ../../
? Можно предположить, что мы знаем имя проекта.
Ответы
Ответ 1
Вы можете сделать это так, как это делает Django: определить переменную для корня проекта из файла, который находится на верхнем уровне проекта. Например, если так выглядит структура вашего проекта:
project/
configuration.conf
definitions.py
main.py
utils.py
В definitions.py
вы можете определить (это требует import os
):
ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) # This is your Project Root
Таким образом, с известным корнем проекта вы можете создать переменную, которая указывает на местоположение конфигурации (это может быть определено где угодно, но логичным было бы поместить ее в место, где определены константы - например, definitions.py
):
CONFIG_PATH = os.path.join(ROOT_DIR, 'configuration.conf') # requires 'import os'
Затем вы можете легко получить доступ к константе (в любом другом файле) с помощью оператора import (например, в utils.py
): from definitions import CONFIG_PATH
.
Ответ 2
Другие ответы советуют использовать файл на верхнем уровне проекта. В этом нет необходимости, если вы используете pathlib.Path
и parent
(Python 3.4 и выше). Рассмотрим следующую структуру каталогов, в которой все файлы, кроме README.md
и utils.py
, опущены.
project
│ README.md
|
└───src
│ │ utils.py
| | ...
| ...
В utils.py
мы определяем следующую функцию.
from pathlib import Path
def get_project_root() -> Path:
"""Returns project root folder."""
return Path(__file__).parent.parent
Теперь в любом модуле проекта мы можем получить корневой каталог проекта следующим образом.
from src.utils import get_project_root
root = get_project_root()
Преимущества: Любой модуль, который вызывает get_project_root
, можно перемещать без изменения поведения программы. Только когда модуль utils.py
перемещен, мы должны обновить get_project_root
и импортировать (инструменты автоматизации рефакторинга могут использоваться для этого).
Ответ 3
Чтобы получить путь к "корневому" модулю, вы можете использовать:
import os
import sys
os.path.dirname(sys.modules['__main__'].__file__)
Но что более интересно, если у вас есть конфигурационный "объект" в вашем самом верхнем модуле, вы можете прочитать его следующим образом:
app = sys.modules['__main__']
stuff = app.config.somefunc()
Ответ 4
Все предыдущие решения кажутся слишком сложными для того, что, я думаю, вам нужно, и часто не работают для меня. Следующая однострочная команда делает то, что вы хотите:
import os
ROOT_DIR = os.path.abspath(os.curdir)
Ответ 5
Стандартным способом достижения этого является использование модуля pkg_resources
, который является частью пакета setuptools
. setuptools
используется для создания установочного пакета python.
Вы можете использовать pkg_resources
, чтобы вернуть содержимое нужного файла в виде строки, и вы можете использовать pkg_resources
, чтобы получить фактический путь к нужному файлу в вашей системе.
Скажем, что у вас есть пакет под названием stackoverflow
.
stackoverflow/
|-- app
| `-- __init__.py
`-- resources
|-- bands
| |-- Dream\ Theater
| |-- __init__.py
| |-- King's\ X
| |-- Megadeth
| `-- Rush
`-- __init__.py
3 directories, 7 files
Теперь скажем, что вы хотите получить доступ к файлу Rush из модуля app.run
. Используйте pkg_resources.resouces_filename
, чтобы получить путь к Rush и pkg_resources.resource_string
, чтобы получить содержимое Rush; Таким образом:
import pkg_resources
if __name__ == "__main__":
print pkg_resources.resource_filename('resources.bands', 'Rush')
print pkg_resources.resource_string('resources.bands', 'Rush')
Выход:
/home/sri/workspace/stackoverflow/resources/bands/Rush
Base: Geddy Lee
Vocals: Geddy Lee
Guitar: Alex Lifeson
Drums: Neil Peart
Это работает для всех пакетов на вашем пути python. Поэтому, если вы хотите знать, где lxml.etree
существует в вашей системе:
import pkg_resources
if __name__ == "__main__":
print pkg_resources.resource_filename('lxml', 'etree')
выход:
/usr/lib64/python2.7/site-packages/lxml/etree
Дело в том, что вы можете использовать этот стандартный метод для доступа к файлам, которые установлены в вашей системе (например, pip install xxx или yum -y install python-xxx) и файлы, которые находятся в модуле, который вы сейчас работаете.
Ответ 6
Недавно я пытался сделать что-то подобное, и я нашел эти ответы не подходящими для моих сценариев использования (распределенная библиотека, которая должна обнаруживать корень проекта). В основном я боролся с различными средами и платформами и до сих пор не нашел что-то совершенно универсальное.
Код локальный для проекта
Я видел этот пример, упомянутый и используемый в нескольких местах, Django и т.д.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Как это просто, это работает, только когда файл, в котором находится фрагмент, на самом деле является частью проекта. Мы не получаем каталог проекта, а вместо этого каталог фрагмента
Аналогичным образом, sys.modules подход разбивает когда вызывается из - за пределов EntryPoint применения, в частности, я заметил ребенок нить не может определить это без всякого отношения назад к "основному" модуля. Я явно поместил импорт внутри функции, чтобы продемонстрировать импорт из дочернего потока, переместив его на верхний уровень 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+, поэтому мы ищем все потоки в поисках основного потока, сохраняя его ID. Если основной поток уже завершен, он не будет указан в threading.enumerate()
. В этом случае мы RuntimeError()
пока не найду лучшее решение.
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")
Далее мы находим самый первый кадр стека основного потока. Используя специальную функцию 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
Ответ 7
Пытаться:
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Ответ 8
Это сработало для меня, используя стандартный проект PyCharm с моей виртуальной средой (venv) в корневом каталоге проекта.
Код ниже не самый красивый, но постоянно получает корень проекта. Он возвращает полный путь к каталогу к venv из VIRTUAL_ENV
среды VIRTUAL_ENV
например /Users/NAME/documents/PROJECT/venv
Затем он разделяет путь в последнем /
, давая массив с двумя элементами. Первым элементом будет путь к проекту, например, /Users/NAME/documents/PROJECT
import os
print(os.path.split(os.environ['VIRTUAL_ENV'])[0])
Ответ 9
Я тоже боролся с этой проблемой, пока не пришел к этому решению.
На мой взгляд, это самое чистое решение.
В свои setup.py добавьте "пакеты"
setup(
name='package_name'
version='0.0.1'
.
.
.
packages=['package_name']
.
.
.
)
В вашем python_script.py
import pkg_resources
import os
resource_package = pkg_resources.get_distribution(
'package_name').location
config_path = os.path.join(resource_package,'configuration.conf')
Ответ 10
Если вы работаете с anaconda-project, вы можете запросить PROJECT_ROOT из переменной среды → os.getenv('PROJECT_ROOT'). Это работает только в том случае, если скрипт выполняется посредством запуска проекта anaconda.
Если вы не хотите, чтобы ваш скрипт выполнялся программой anaconda-project, вы можете запросить абсолютный путь к исполняемому двоичному файлу интерпретатора Python, который вы используете, и извлечь строку пути до каталога envs exclusiv. Например: интерпретатор Python моего conda env находится по адресу:
/home/user/project_root/envs/default/bin/python
# You can first retrieve the env variable PROJECT_DIR.
# If not set, get the python interpreter location and strip off the string till envs inclusiv...
if os.getenv('PROJECT_DIR'):
PROJECT_DIR = os.getenv('PROJECT_DIR')
else:
PYTHON_PATH = sys.executable
path_rem = os.path.join('envs', 'default', 'bin', 'python')
PROJECT_DIR = py_path.split(path_rem)[0]
Это работает только с conda-проектом с фиксированной структурой проекта анаконды-проекта