Python - Получить путь к корневой структуре проекта
У меня есть проект Python с файлом конфигурации в корне проекта. Файл конфигурации должен быть доступен в нескольких разных файлах по всему проекту.
Так это выглядит примерно так: <ROOT>/configuration.conf
<ROOT>/A/a.py
, <ROOT>/A/B/b.py
(когда b,a.py получить доступ к файлу конфигурации).
Какой самый лучший / самый простой способ получить путь к корню проекта и файлу конфигурации, независимо от того, в каком файле внутри проекта я нахожусь? т.е. без использования ../../
? Можно предположить, что мы знаем имя корня проекта.
25 ответов
Вы можете сделать это так, как это делает 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
,
Другие ответы советуют использовать файл на верхнем уровне проекта. Это не обязательно, если вы используете pathlib.Path
а также parent
, Рассмотрим следующую структуру каталогов, где все файлы, кроме 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
и импорт (используйте рефакторинг IDE, чтобы автоматизировать это).
Все предыдущие решения кажутся слишком сложными для того, что, я думаю, вам нужно, и часто не работают для меня. Следующая однострочная команда делает то, что вы хотите:
import os
ROOT_DIR = os.path.abspath(os.curdir)
Код ниже Возвращает путь до корня вашего проекта
import sys
print(sys.path[1])
Чтобы получить путь к "корневому" модулю, вы можете использовать:
import os
import sys
os.path.dirname(sys.modules['__main__'].__file__)
Но что более интересно, если у вас есть конфигурационный "объект" в вашем самом верхнем модуле, вы можете прочитать его следующим образом:
app = sys.modules['__main__']
stuff = app.config.somefunc()
Стандартный способ достичь этого - использовать pkg_resources
модуль, который является частью setuptools
пакет. setuptools
используется для создания устанавливаемого пакета Python.
Ты можешь использовать pkg_resources
вернуть содержимое нужного файла в виде строки, и вы можете использовать pkg_resources
чтобы получить фактический путь к нужному файлу в вашей системе.
Допустим, у вас есть пакет под названием stackru
,
stackru/
|-- 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
получить содержимое 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/stackru/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) и файлам, которые находятся в модуле, над которым вы сейчас работаете.
Пытаться:
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Просто и динамично!
это решение работает на любой ОС и на любом уровне каталога:
Предполагая, что имя папки вашего проекта
my_project
from pathlib import Path
current_dir = Path(__file__)
project_dir = [p for p in current_dir.parents if p.parts[-1]=='my_project'][0]
Недавно я пытался сделать что-то подобное, и я нашел эти ответы не подходящими для моих сценариев использования (распределенная библиотека, которая должна обнаруживать корень проекта). В основном я боролся с различными средами и платформами, но до сих пор не нашел чего-то совершенно универсального.
Код локальный для проекта
Я видел этот пример, упомянутый и используемый в нескольких местах, 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()
в этом случае пока не найду лучшего решения.
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] Наконец, различия между реализациями Windows и Linux inspect.getouterframes()
должны быть обработаны. Используя очищенное имя файла, 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
Вот пакет, который решает эту проблему: from-root
pip install from-root
from from_root import from_root, from_here
# path to config file at the root of your project
# (no matter from what file of the project the function is called!)
config_path = from_root('config.json')
# path to the data.csv file at the same directory where the callee script is located
# (has nothing to do with the current working directory)
data_path = from_here('data.csv')
Перейдите по ссылке выше и прочтите файл readme, чтобы увидеть другие варианты использования.
Решил для себя следующее.
Необходимо получить путь к «MyProject / drivers» из основного файла.
MyProject/
├─── RootPackge/
│ ├── __init__.py
│ ├── main.py
│ └── definitions.py
│
├─── drivers/
│ └── geckodriver.exe
│
├── requirements.txt
└── setup.py
definitions.py
Поместите не в корень проекта, а в корень основного пакета
from pathlib import Path
ROOT_DIR = Path(__file__).parent.parent
Используйте ROOT_DIR:
main.py
# imports must be relative,
# not from the root of the project,
# but from the root of the main package.
# Not this way:
# from RootPackge.definitions import ROOT_DIR
# But like this:
from definitions import ROOT_DIR
# Here we use ROOT_DIR
# get path to MyProject/drivers
drivers_dir = ROOT_DIR / 'drivers'
# Thus, you can get the path to any directory
# or file from the project root
driver = webdriver.Firefox(drivers_dir)
driver.get('http://www.google.com')
Тогда PYTHON_PATH не будет использоваться для доступа к файлу definitions.py.
Работает в PyCharm:
запустите файл main.py (ctrl + shift + F10 в Windows)
Работает в CLI из корня проекта:
$ py RootPackge/main.py
Работает в CLI из RootPackge:
$ cd RootPackge
$ py main.py
Работает из каталогов над проектом:
$ cd ../../../../
$ py MyWork/PythoProjects/MyProject/RootPackge/main.py
Работает откуда угодно, если указать абсолютный путь к основному файлу.
Не зависит от венв.
Это сработало для меня, используя стандартный проект 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])
Я тоже боролся с этой проблемой, пока не пришел к этому решению. Это самое чистое решение на мой взгляд.
В вашем 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')
Мне пришлось реализовать собственное решение, потому что это не так просто, как вы думаете. Мое решение основано на проверке трассировки стека (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
Просто пример: я хочу запустить 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)
Вот мой взгляд на этот вопрос.
У меня есть простой пример использования, который какое-то время меня беспокоил. Пробовал несколько решений, но ни одно из них не понравилось достаточно гибким.
Итак, вот что я выяснил.
- создайте пустой файл python в корневом каталоге -> я вызываю это
(при условии, что корень проекта находится в PYTHONPATH, поэтому его можно импортировать) - добавить несколько строк в мой модуль / класс, который я называю здесь.
Это импортирует модуль и получит путь к этому модулю.
Вот пример структуры проекта
this_project
├── beacon.py
├── lv1
│ ├── __init__.py
│ └── lv2
│ ├── __init__.py
│ └── not_in_root.py
...
Содержание
not_in_root.py
import os
from pathlib import Path
class Config:
try:
import beacon
print(f"'import beacon' -> {os.path.dirname(os.path.abspath(beacon.__file__))}") # only for demo purposes
print(f"'import beacon' -> {Path(beacon.__file__).parent.resolve()}") # only for demo purposes
except ModuleNotFoundError as e:
print(f"ModuleNotFoundError: import beacon failed with {e}. "
f"Please. create a file called beacon.py and place it to the project root directory.")
project_root = Path(beacon.__file__).parent.resolve()
input_dir = project_root / 'input'
output_dir = project_root / 'output'
if __name__ == '__main__':
c = Config()
print(f"Config.project_root: {c.project_root}")
print(f"Config.input_dir: {c.input_dir}")
print(f"Config.output_dir: {c.output_dir}")
Результат будет
/home/xyz/projects/this_project/venv/bin/python /home/xyz/projects/this_project/lv1/lv2/not_in_root.py
'import beacon' -> /home/xyz/projects/this_project
'import beacon' -> /home/xyz/projects/this_project
Config.project_root: /home/xyz/projects/this_project
Config.input_dir: /home/xyz/projects/this_project/input
Config.output_dir: /home/xyz/projects/this_project/output
Конечно, это не нужно называть
beacon.py
Использование пустого файла .py гарантирует, что он не будет перемещен в другое место из-за некоторого будущего рефакторинга.
Ваше здоровье
Я использовал метод../, чтобы получить текущий путь к проекту.
Пример: Project1 - D:\projects
src
Файлы конфигурации
Configuration.cfg
Путь ="../src/ConfigurationFiles/Configuration.cfg"
Для этого вы можете добавить корневой каталог вашего репозитория кода в путь Python. Вы можете сделать это, добавив следующие строки кода в начало вашего скрипта:
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
Этот код добавляет родительский каталог текущего файла (который, как предполагается, находится в подпапке корневого каталога) к пути Python.
Однолинейное решение
Всем привет! У меня всегда была эта проблема, и ни одно из решений не помогло мне, поэтому я использовал аналогичный подход, которыйhere::here()
использует вR
.
Установите
groo
упаковка:pip install groo-ozika
Поместите скрытый файл в корневой каталог, например
.my_hidden_root_file
.Затем из любого места ниже в иерархии каталогов (т.е. в корневом каталоге) выполните следующее:
from groo.groo import get_root
root_folder = get_root(".my_hidden_root_file")
- Вот и все!
Он просто выполняет следующую функцию:
def get_root(rootfile):
import os
from pathlib import Path
d = Path(os.getcwd())
found = 0
while found == 0:
if os.path.isfile(os.path.join(d, rootfile)):
found = 1
else:
d=d.parent
return d
Это не совсем ответ на этот вопрос; Но это может кому-то помочь. Фактически, если вы знаете названия папок, вы можете это сделать.
import os
import sys
TMP_DEL = '×'
PTH_DEL = '\\'
def cleanPath(pth):
pth = pth.replace('/', TMP_DEL)
pth = pth.replace('\\', TMP_DEL)
return pth
def listPath():
return sys.path
def getPath(__file__):
return os.path.abspath(os.path.dirname(__file__))
def getRootByName(__file__, dirName):
return getSpecificParentDir(__file__, dirName)
def getSpecificParentDir(__file__, dirName):
pth = cleanPath(getPath(__file__))
dirName = cleanPath(dirName)
candidate = f'{TMP_DEL}{dirName}{TMP_DEL}'
if candidate in pth:
pth = (pth.split(candidate)[0]+TMP_DEL +
dirName).replace(TMP_DEL*2, TMP_DEL)
return pth.replace(TMP_DEL, PTH_DEL)
return None
def getSpecificChildDir(__file__, dirName):
for x in [x[0] for x in os.walk(getPath(__file__))]:
dirName = cleanPath(dirName)
x = cleanPath(x)
if TMP_DEL in x:
if x.split(TMP_DEL)[-1] == dirName:
return x.replace(TMP_DEL, PTH_DEL)
return None
Список доступных папок:
print(listPath())
Использование:
#Directories
#ProjectRootFolder/.../CurrentFolder/.../SubFolder
print(getPath(__file__))
# c:\ProjectRootFolder\...\CurrentFolder
print(getRootByName(__file__, 'ProjectRootFolder'))
# c:\ProjectRootFolder
print(getSpecificParentDir(__file__, 'ProjectRootFolder'))
# c:\ProjectRootFolder
print(getSpecificParentDir(__file__, 'CurrentFolder'))
# None
print(getSpecificChildDir(__file__, 'SubFolder'))
# c:\ProjectRootFolder\...\CurrentFolder\...\SubFolder
Если вы работаете с anaconda-project, вы можете запросить PROJECT_ROOT из переменной среды -> os.getenv('PROJECT_ROOT'). Это работает только в том случае, если скрипт выполняется через запуск проекта anaconda.
Если вы не хотите, чтобы ваш скрипт запускался anaconda-project, вы можете запросить абсолютный путь к исполняемому двоичному файлу интерпретатора Python, который вы используете, и извлечь строку пути до каталога envs exclusiv. Например: интерпретатор python моего conda env находится по адресу:
/ домашний / пользователь / каталог-проекта / envs / по умолчанию /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-project с фиксированной структурой проекта anaconda-project.
В итоге мне пришлось делать это в разных ситуациях, когда разные ответы работали правильно, другие - нет, или либо с различными модификациями, поэтому я сделал этот пакет, чтобы он работал в большинстве ситуаций.
pip install get-project-root
from get_project_root import root_path
project_root = root_path(ignore_cwd=False)
# >> "C:/Users/person/source/some_project/"
В корневом каталоге проекта нет . Я решил эту проблему, найдя каталог-предок, в котором нет__init__.py
.
from functools import lru_cache
from pathlib import Path
@lru_cache()
def get_root_dir() -> str:
path = Path().cwd()
while Path(path, "__init__.py").exists():
path = path.parent
return str(path)
Здесь много ответов, но я не смог найти чего-то простого, охватывающего все случаи, поэтому позвольте мне также предложить свое решение:
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
На момент написания ни одно из других решений не было самодостаточным. Они зависят либо от переменной окружения, либо от положения модуля в структуре пакета. Главный ответ с решением "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
файл.