Python - 获取根项目结构的路径

时间:2014-08-19 17:02:46

标签: python

我在项目根目录中有一个配置文件的python项目。 需要在整个项目中的几个不同文件中访问配置文件。

所以它看起来像:<ROOT>/configuration.conf <ROOT>/A/a.py<ROOT>/A/B/b.py(当b,a.py访问配置文件时)。

获取项目根目录和配置文件的路径的最佳/最简单方法是什么,而不依赖于我所在项目中的哪个文件?即不使用../../?可以假设我们知道项目根目录。

20 个答案:

答案 0 :(得分:98)

您可以这样做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

答案 1 :(得分:18)

获取&#34; root&#34;的路径模块,您可以使用:

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

但更有趣的是,如果你有一个配置&#34;对象&#34;在你最顶层的模块中你可以像这样读取它:

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

答案 2 :(得分:15)

其他答案建议在项目的顶层使用文件。如果使用pathlib.Pathparent,则没有必要。考虑以下目录结构,其中除README.mdutils.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重构来使它自动化)。

答案 3 :(得分:10)

以下代码返回直到您的项目根目录的路径

main()

答案 4 :(得分:9)

实现此目标的一种标准方法是使用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

现在让我们说您要从模块app.run访问文件Rush。使用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)以及您当前所在模块中的文件工作。

答案 5 :(得分:5)

以前所有的解决方案对于我认为您需要的东西似乎都过于复杂,并且通常不适用于我。下面的单行命令可以满足您的要求:

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

答案 6 :(得分:4)

我自己决定如下。
需要从主文件中获取“MyProject/drivers”的路径。

MyProject/
├─── RootPackge/
│    ├── __init__.py
│    ├── main.py
│    └── definitions.py
│
├─── drivers/
│    └── geckodriver.exe
│
├── requirements.txt
└── setup.py

定义.py
不是放在项目的根目录,而是放在主包的根目录

from pathlib import Path

ROOT_DIR = Path(__file__).parent.parent

使用 ROOT_DIR:
主文件

# 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”(在 Windows 中按 ctrl + shift + F10)

从项目根目录在 CLI 中工作:

$ py RootPackge/main.py

在来自 RootPackge 的 CLI 中工作:

$ cd RootPackge
$ py main.py

从项目上方的目录工作:

$ cd ../../../../
$ py MyWork/PythoProjects/MyProject/RootPackge/main.py

如果您提供主文件的绝对路径,则可以在任何地方使用。
不依赖于 venv。

答案 7 :(得分:3)

仅举一个例子:我想从 helper1.py

中运行 runio.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)

答案 8 :(得分:2)

这是解决该问题的软件包: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')

查看上面的链接并阅读自述文件以查看更多用例

答案 9 :(得分:1)

这对我使用标准的PyCharm项目和我的虚拟环境(venv)在项目根目录下工作。

下面的代码不是最漂亮的,但始终是项目的根目录。它从VIRTUAL_ENV环境变量返回venv的完整目录路径,例如/Users/NAME/documents/PROJECT/venv

然后在最后/分割路径,给出一个包含两个元素的数组。第一个元素是项目路径,例如/Users/NAME/documents/PROJECT

import os

print(os.path.split(os.environ['VIRTUAL_ENV'])[0])

答案 10 :(得分: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))

打破这一点: 首先,我们要准确地找到主线程的线程ID。在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))

到目前为止,我已经在Windows的Python2.7和3.6以及WSL的Python3.4上对此进行了测试

答案 11 :(得分:1)

我使用../方法获取当前项目路径。

示例: Project1-D:\ projects

src

ConfigurationFiles

Configuration.cfg

Path =“ ../ src / ConfigurationFiles / Configuration.cfg”

答案 12 :(得分:1)

我必须实现一个自定义解决方案,因为它没有您想象的那么简单。 我的解决方案基于堆栈跟踪检查(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

答案 13 :(得分:0)

我最终需要在各种不同的情况下执行此操作,其中不同的答案正确工作,其他人没有,或者进行了各种修改,所以我使这个包适用于大多数情况

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/"

https://pypi.org/project/get-project-root/

答案 14 :(得分:0)

这是我对这个问题的看法。

我有一个简单的用例困扰了我一段时间。尝试了一些解决方案,但我不喜欢它们中的任何一个足够灵活。

这就是我想出来的。

  • 在根目录中创建一个空白的 python 文件 -> 我称之为 beacon.py
    (假设项目根目录在 PYTHONPATH 中,因此可以导入)
  • 在我的模块/类中添加几行,我称之为 not_in_root.py
    这将导入 beacon.py 模块并获取该模块的路径 模块

这是一个示例项目结构

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,也不需要为空,基本上任何python文件(可导入)文件都可以,只要它在根目录中即可。

使用空的 .py 文件可以保证它不会因未来的重构而移到别处。

干杯

答案 15 :(得分: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文件中)工作。

答案 16 :(得分:0)

如果您正在使用anaconda-project,则可以从环境变量-> os.getenv('PROJECT_ROOT')查询PROJECT_ROOT。仅当脚本通过anaconda-project run执行时有效。

如果您不希望您的脚本由anaconda-project运行,则可以查询正在使用的Python解释器的可执行二进制文件的绝对路径,并将路径字符串提取到envs目录exclusiv中。例如:我的conda env的python解释器位于:

  

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

这仅适用于具有固定项目结构(anaconda-project)的conda-project

答案 17 :(得分: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')

答案 18 :(得分:0)

尝试:

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

答案 19 :(得分:-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