Python - Отримайте шлях до кореневої структури проекту


127

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

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

Який найкращий / найпростіший спосіб отримати шлях до кореня проекту та файлу конфігурації, не залежно від того, який файл у проекті я перебуваю? тобто без використання ../../? Добре припустити, що ми знаємо ім'я кореня проекту.


чи <ROOT>/__init__.pyіснує?
mgilson

Або ваш файл конфігурації є модулем python, і ви можете легко отримати доступ до нього просто за допомогою імпорту, або це не модуль python, і вам слід помістити його у відоме місце. Наприклад, $ HOME / .my_project / my_project.conf.
Джон Сміт Необов’язково

@JohnSmithOfault - це файл JSON. Мені потрібно мати доступ до нього за допомогою шляху. Так. Усі папки містять його.
Shookie

_ Добре припустити, що ми знаємо ім'я кореня проекту._ Це означає, що ви знаєте шлях до проекту? Хіба це не просто os.path.join (відоме_рут_ім'я, "config.conf")?
tdelaney

Якщо це конфігурація користувача, я зазвичай використовую щось подібне os.path.expanduser('~/.myproject/myproject.conf'). Він працює на Unix та Windows.
Джон Сміт Необов’язково

Відповіді:


157

Ви можете зробити це так, як це робить 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`

Потім, ви можете легко отримати доступ до постійної (в будь-який з інших файлів) з оператором імпорту (наприклад , в utils.py): from definitions import CONFIG_PATH.


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та імпортувати (для цього автоматизовано можна використовувати інструменти рефакторингу).


2
Будь-який модуль, що знаходиться в корені. Виклик src.utils поза коренем не повинен працювати. Я помиляюся?
aerijman

ім'я " файл " не визначено, чому?
Лук Арон

26

Усі попередні рішення здаються надто складними для того, що я думаю, що вам потрібно, і часто не працювали для мене. Наступна однорядкова команда виконує те, що ви хочете:

import os
ROOT_DIR = os.path.abspath(os.curdir)

3
Помістіть це у config.py, у корені каталогу, .. bamn! Ти отримав собі одиноку.
swdev

2
Цей метод передбачає, що ви запускаєте програму в межах шляху, який вона існує. Багато "користувачів" мають піктограму, яку натискають із робочого столу, або можуть запускати додаток цілком з іншого каталогу.
DevPlayer

23

Щоб отримати шлях модуля "root", ви можете використовувати:

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

Але що цікавіше, якщо у найпопулярнішому модулі у вас є конфігураційний "об'єкт", ви можете з нього прочитати так:

app = sys.modules['__main__']
stuff = app.config.somefunc()

1
Тут osза замовчуванням доступно. Потрібно імпортувати os. Тож додавання рядка import osзробить відповідь більш повною.
Пані Абу Нафей Ібна Захід

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для отримання шляху до 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) та файлів, що знаходяться в модулі, над яким ви зараз працюєте.


1
Мені подобається твій вибір групи!
dylan_fan

4

Нижче Код повертає шлях до кореня проекту

import sys
print(sys.path[1])

Гарна порада! Цікаво, чому ніхто не підтримав вашу відповідь, крім мене: P
daveoncode

Дякую, Дейвон, дуже ціную це !!
Арпан Саїні

На жаль , це не так, просто: P ... дивитися на моє повне рішення: stackoverflow.com/a/62510836/267719
daveoncode

3

Спробуйте:

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

1
Це саме те, що мені було потрібно. Просте рішення, працює для мене, тому що моя структура була root-> config-> conf.py, я хотів визначити корінь проекту в conf.py, а root був рівно на два рівні від цього файлу.
Даніял Аршад

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])

3
Це не буде працювати з такими налаштуваннями, як anaconda або pipenv, оскільки віртуальне середовище не міститься в проекті в цих випадках.
Грипп

1

Нещодавно я намагався зробити щось подібне, і я виявив ці відповіді недостатніми для моїх випадків використання (розподілена бібліотека, яка потребує виявлення кореня проекту). В основному я бився в різних середовищах і платформах, і досі не знайшов щось ідеально універсальне.

Код локальний для проекту

Я бачив, як цей приклад згадувався та використовувався в кількох місцях, Джанго тощо.

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+, тому ми шукаємо всі потоки, шукаючи основний потік, окрім його 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")

Далі ми знаходимо найперший кадр стека основної нитки. Використовуючи специфічну функцію 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_ROOT із змінної середовища -> os.getenv ("PROJECT_ROOT"). Це працює лише в тому випадку, якщо сценарій виконується за допомогою проекту anaconda.

Якщо ви не хочете, щоб ваш скрипт виконувався за допомогою anaconda-проекта, ви можете запитати абсолютний шлях виконуваного двійкового файлу інтерпретатора Python, який ви використовуєте, і витягти рядок шляху до каталогу envs виключно. Наприклад: Інтерпретатор пітона мого 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-проектом із фіксованою структурою проекту анаконди-проекту


0

Я використовував метод ../, щоб отримати поточний шлях проекту.

Приклад: Project1 - D: \ проекти

src

Конфігураційні файли

Configuration.cfg

Шлях = "../ src / ConfigurationFiles / Configuration.cfg"


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файл верхнього рівня .


Чи можете ви додати короткий опис свого рішення та як вони можуть використовувати його як своє рішення?
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

-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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.