Подтвердить что ты не робот

Python - получить путь к корневой структуре проекта

У меня есть проект python с конфигурационным файлом в корне проекта. Файл конфигурации должен быть доступен в нескольких файлах по всему проекту.

Итак, это выглядит примерно так: <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py (когда b, a.py обращается к файлу конфигурации).

Какой лучший/самый простой способ получить путь к корню проекта и конфигурационному файлу без зависимости от того, в каком файле находится внутри проекта? без использования ../../? Можно предположить, что мы знаем имя проекта.

4b9b3361

Ответ 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-проектом с фиксированной структурой проекта анаконды-проекта