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

128

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

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

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

Shookie
источник
действительно <ROOT>/__init__.pyсуществует?
mgilson
Либо ваш файл конфигурации является модулем python, и вы можете легко получить к нему доступ, просто используя оператор импорта, либо это не модуль python, и вы должны поместить его в хорошо известное место. Например, $ HOME / .my_project / my_project.conf.
Джон Смит Необязательно,
@JohnSmithOptional - это файл JSON. Мне нужно получить к нему доступ, используя путь. Да. Все папки включают его.
Shookie
_ Можно предположить, что мы знаем имя корня проекта. _ Означает ли это, что вы знаете путь к проекту? Разве это не просто os.path.join (известное_корневое_имя, "configuration.conf")?
tdelaney
Если это пользовательская конфигурация, я бы обычно использовал что-то вроде os.path.expanduser('~/.myproject/myproject.conf'). Работает в Unix и Windows.
Джон Смит Необязательно,

Ответы:

159

Вы можете сделать это так, как это делает 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

Таким образом, зная Project Root , вы можете создать переменную, которая указывает на расположение конфигурации (это может быть определено где угодно, но логичным местом было бы поместить ее в место, где определены константы - например definitions.py):

CONFIG_PATH = os.path.join(ROOT_DIR, 'configuration.conf')  # requires `import os`

Затем, вы можете легко получить доступ к постоянной (в любой из других файлов) с оператором импорта (например , в utils.py): from definitions import CONFIG_PATH.

jrd1
источник
1
Чтобы включить такой файл definitions.py, потребуется ли также добавить __init__.pyфайл в корневой каталог проекта? Это должно быть правильно? Я только начал с Python и не уверен в лучших практиках. Спасибо.
akskap
3
@akskap: Нет, в этом __init__.pyне будет необходимости, поскольку этот файл требуется только при определении пакетов: Эти __init__.pyфайлы необходимы , чтобы Python лечить каталоги как содержащие пакеты; это сделано для того, чтобы каталоги с общим именем, таким как строка, случайно не скрывали допустимые модули, которые появляются позже на пути поиска модулей. В простейшем случае это __init__.pyможет быть просто пустой файл, но он также может выполнять код инициализации для пакета или устанавливать __all__переменную, как описано ниже. См .: docs.python.org/3/tutorial/modules.html#packages
jrd1,
Мне любопытно с точки зрения стиля, допустимо ли добавлять эти определения в __init.py__корневой пакет. Это сэкономило бы создание другого файла, а также позволило бы более удобный синтаксис from root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6
@ Johndt6: соглашение должно оставаться __init__.pyпустым, но это не совсем так (в конце концов, это соглашение). Смотрите это для более: stackoverflow.com/questions/2361124/using-init-py
jrd1
1
Не @JavNoor: нет - в данном примере вы цитируемой, os.path.abspathзвонит строку, '__file__'. Напомним, что __file__на самом деле это атрибут импорта, определенный для модулей Python. В этом случае __file__вернет путь, с которого загружен модуль. Подробнее здесь (см. Раздел о модулях): docs.python.org/3/reference/datamodel.html
jrd1
62

Другие ответы советуют использовать файл на верхнем уровне проекта. В этом нет необходимости, если вы используете 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:
    return Path(__file__).parent.parent

В любом модуле проекта мы теперь можем получить корень проекта следующим образом.

from src.utils import get_project_root

root = get_project_root()

Преимущества : Любой модуль, вызовы которого get_project_rootможно перемещать без изменения поведения программы. Только когда модуль utils.pyперемещается, мы должны обновлять get_project_rootи импортировать (для автоматизации этого можно использовать инструменты рефакторинга).

RikH
источник
2
Любой модуль, который находится в корне. Вызов src.utils извне не должен работать. Я ошибся?
Aerijman
имя ' файл ' не определено, почему?
Лук Арон,
26

Все предыдущие решения кажутся слишком сложными для того, что вам нужно, и часто у меня не работали. Следующая однострочная команда делает то, что вы хотите:

import os
ROOT_DIR = os.path.abspath(os.curdir)
Martim
источник
3
Поместите это в config.py в корень каталога, .. бац! Вы получили синглтон.
swdev
2
Этот метод предполагает, что вы запускаете приложение по пути, по которому оно существует. Многие «пользователи» имеют значок, который они нажимают на рабочем столе, или могут полностью запустить приложение из другого каталога.
DevPlayer
23

Чтобы получить путь к "корневому" модулю, вы можете использовать:

import os
import sys
os.path.dirname(sys.modules['__main__'].__file__)

Но что более интересно, если у вас есть конфигурационный «объект» в самом верхнем модуле, вы можете-прочитать- из него следующим образом:

app = sys.modules['__main__']
stuff = app.config.somefunc()
DevPlayer
источник
1
Здесь osпо умолчанию не доступны. Нужно импортировать os. Таким образом, добавление строки import osсделает ответ более полным.
Md. Abu Nafee Ibna Zahid
5
Это дает каталог, содержащий выполненный сценарий. Например, при запуске python3 -m topmodule.submodule.scriptвыдаст /path/to/topmodule/submoduleвместо /path/to/topmodule.
danijar
14

Стандартный способ добиться этого - использовать 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чтобы получить путь к Рашу и pkg_resources.resource_stringполучить содержимое Раша; Таким образом:

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) и файлам, которые находятся в модуле, над которым вы сейчас работаете.

землеройка
источник
1
Мне нравится твой выбор группы!
dylan_fan
4

Пытаться:

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Гарри
источник
1
Это именно то, что мне нужно. Простое решение, работает для меня, потому что моя структура была root-> config-> conf.py. Я хотел определить корень проекта в conf.py, а root был ровно на два уровня выше этого файла.
Даниал Аршад
4

Код ниже Возвращает путь до корня вашего проекта

import sys
print(sys.path[1])
Арпан Сайни
источник
Хороший совет! Интересно, почему за твой ответ никто не проголосовал, кроме меня: P
daveoncode
Спасибо, Дэйвон. Очень ценю это !!
Арпан Сайни,
К сожалению, это не так просто: P ... взгляните на мое полное решение: stackoverflow.com/a/62510836/267719
daveoncode
2

Я тоже боролся с этой проблемой, пока не пришел к этому решению. На мой взгляд, это самое чистое решение.

В вашем 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')
парень
источник
Использование виртуальной среды и установка пакета с python3 setup.py installней больше не указывали на папку с исходным кодом, а на яйцо внутри ~./virtualenv/..../app.egg. Поэтому мне пришлось включить файл конфигурации в установку пакета.
loxosceles
2

Просто пример: я хочу запустить runio.py из helper1.py

Пример дерева проекта:

myproject_root
- modules_dir/helpers_dir/helper1.py
- tools_dir/runio.py

Получите рут проекта:

import os
rootdir = os.path.dirname(os.path.realpath(__file__)).rsplit(os.sep, 2)[0]

Путь сборки к скрипту:

runme = os.path.join(rootdir, "tools_dir", "runio.py")
execfile(runme)
Алексей Грановский
источник
1

Это сработало для меня, используя стандартный проект PyCharm с моей виртуальной средой (venv) в корневом каталоге проекта.

Код ниже не самый красивый, но последовательно получает корень проекта. Он возвращает полный путь к каталогу venv изVIRTUAL_ENV переменной среды, например/Users/NAME/documents/PROJECT/venv

Затем он разделяет путь последним /, давая массив из двух элементов. Первым элементом будет путь к проекту, например/Users/NAME/documents/PROJECT

import os

print(os.path.split(os.environ['VIRTUAL_ENV'])[0])
Gaz_Edge
источник
3
Это не будет работать с такими настройками, как anaconda или pipenv, поскольку в этих случаях виртуальная среда не содержится в проекте.
Gripp
1

Недавно я пытался сделать что-то подобное, и я нашел эти ответы неадекватными для моих вариантов использования (распределенная библиотека, которая должна определять корень проекта). В основном я боролся с разными средами и платформами, но до сих пор не нашел чего-то совершенно универсального.

Используйте локальный код для проекта

Я видел, как этот пример упоминался и использовался в нескольких местах, в 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.

Джозеф Бурниц
источник
0

Если вы работаете с anaconda-project, вы можете запросить PROJECT_ROOT из переменной среды -> os.getenv ('PROJECT_ROOT'). Это работает только в том случае, если скрипт выполняется через запуск проекта anaconda.

Если вы не хотите, чтобы ваш скрипт запускался anaconda-project, вы можете запросить абсолютный путь к исполняемому двоичному файлу интерпретатора Python, который вы используете, и извлечь строку пути до каталога envs exclusiv. Например: интерпретатор python моего conda env находится по адресу:

/ Главная / пользователь / project_root / envs / по умолчанию / bin / питон

# 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-project с фиксированной структурой проекта anaconda-project.

Domsch
источник
0

Я использовал метод ../, чтобы получить текущий путь к проекту.

Пример: Project1 - D: \ projects

ЦСИ

ConfigurationFiles

configuration.cfg

Path = "../ SRC / ConfigurationFiles / configuration.cfg"

Adarsh
источник
0

На момент написания ни одно из других решений не было самодостаточным. Они зависят либо от переменной среды, либо от положения модуля в структуре пакета. Главный ответ с решением «Django» становится жертвой последнего, поскольку требует относительного импорта. Он также имеет недостаток, заключающийся в необходимости изменения модуля на верхнем уровне.

Это должен быть правильный подход для поиска пути к каталогу пакета верхнего уровня:

import sys
import os

root_name, _, _ = __name__.partition('.')
root_module = sys.modules[root_name]
root_dir = os.path.dirname(root_module.__file__)

config_path = os.path.join(root_dir, 'configuration.conf')

Он работает, беря первый компонент в пунктирной строке, содержащейся в, __name__и используя его в качестве ключа, в sys.modulesкотором возвращается объект модуля пакета верхнего уровня. Его __file__атрибут содержит путь , который мы хотим после обрезки с /__init__.pyпомощью os.path.dirname().

Это решение является самодостаточным. Он работает где угодно в любом модуле пакета, в том числе в __init__.pyфайле верхнего уровня .

Pyprohly
источник
Не могли бы вы добавить краткое описание своего решения и того, как они могут использовать его в качестве своего решения?
LuRsT
0

Мне пришлось реализовать собственное решение, потому что это не так просто, как вы думаете. Мое решение основано на проверке трассировки стека ( inspect.stack()) + sys.pathи работает нормально независимо от расположения модуля python, в котором вызывается функция, и интерпретатора (я пробовал запускать его в PyCharm, в оболочке поэзии и других ... ). Это полная реализация с комментариями:

def get_project_root_dir() -> str:
    """
    Returns the name of the project root directory.

    :return: Project root directory name
    """

    # stack trace history related to the call of this function
    frame_stack: [FrameInfo] = inspect.stack()

    # get info about the module that has invoked this function
    # (index=0 is always this very module, index=1 is fine as long this function is not called by some other
    # function in this module)
    frame_info: FrameInfo = frame_stack[1]

    # if there are multiple calls in the stacktrace of this very module, we have to skip those and take the first
    # one which comes from another module
    if frame_info.filename == __file__:
        for frame in frame_stack:
            if frame.filename != __file__:
                frame_info = frame
                break

    # path of the module that has invoked this function
    caller_path: str = frame_info.filename

    # absolute path of the of the module that has invoked this function
    caller_absolute_path: str = os.path.abspath(caller_path)

    # get the top most directory path which contains the invoker module
    paths: [str] = [p for p in sys.path if p in caller_absolute_path]
    paths.sort(key=lambda p: len(p))
    caller_root_path: str = paths[0]

    if not os.path.isabs(caller_path):
        # file name of the invoker module (eg: "mymodule.py")
        caller_module_name: str = Path(caller_path).name

        # this piece represents a subpath in the project directory
        # (eg. if the root folder is "myproject" and this function has ben called from myproject/foo/bar/mymodule.py
        # this will be "foo/bar")
        project_related_folders: str = caller_path.replace(os.sep + caller_module_name, '')

        # fix root path by removing the undesired subpath
        caller_root_path = caller_root_path.replace(project_related_folders, '')

    dir_name: str = Path(caller_root_path).name

    return dir_name
daveoncode
источник
-1

Здесь много ответов, но я не смог найти чего-то простого, охватывающего все случаи, поэтому позвольте мне также предложить свое решение:

import pathlib
import os

def get_project_root():
    """
    There is no way in python to get project root. This function uses a trick.
    We know that the function that is currently running is in the project.
    We know that the root project path is in the list of PYTHONPATH
    look for any path in PYTHONPATH list that is contained in this function's path
    Lastly we filter and take the shortest path because we are looking for the root.
    :return: path to project root
    """
    apth = str(pathlib.Path().absolute())
    ppth = os.environ['PYTHONPATH'].split(':')
    matches = [x for x in ppth if x in apth]
    project_root = min(matches, key=len)
    return project_root

alonhzn
источник