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


126

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

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

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


是否<ROOT>/__init__.py存在?
mgilson

您的配置文件都是python模块,您可以使用import语句轻松访问它,或者它不是python模块,您应该将其放置在众所周知的位置。例如$ HOME / .my_project / my_project.conf。
约翰·史密斯可选

@JohnSmithOptional-这是一个JSON文件。我需要能够使用路径访问它。是。所有文件夹都包含它。
菜鸟

_可以假设我们知道项目根目录的名称。_这是否意味着您知道项目的路径?那不就是os.path.join(known_root_name,“ configuration.conf”)吗?
tdelaney 2014年

如果是用户配置,则通常会使用os.path.expanduser('~/.myproject/myproject.conf')。它适用于Unix和Windows。
约翰·史密斯可选

Answers:


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`

然后,你可以很容易地访问与import语句(如中恒(在任何其他文件)utils.pyfrom definitions import CONFIG_PATH


1
要包含这样的definitions.py文件,是否还需要将__init__.py文件添加到根项目目录中?那是正确的吗?我刚开始使用python,不确定最佳实践。谢谢。
akskap '16

3
@akskap:否,__init__.py不需要,因为仅在定义软件包时才需要__init__.py文件__init__.py__all__使Python将目录视为包含软件包文件是必需的;这样做是为了防止具有通用名称的目录(例如字符串)无意间隐藏了稍后在模块搜索路径中出现的有效模块。在最简单的情况下,可以只是一个空文件,但也可以执行该程序包的初始化代码或设置变量,如下所述。 参见: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模块定义的import属性。在这种情况下,__file__将返回从中加载模块的路径名。在此处阅读更多信息(请参阅模块部分):docs.python.org/3/reference/datamodel.html
jrd1

61

其他答案建议在项目的顶层使用文件。如果使用pathlib.Pathparent(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

1
此方法假定您从存在的路径中运行应用程序。许多“用户”都有一个图标,他们可以从桌面上单击该图标,也可以完全从另一个目录运行该应用程序。
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将使答案更完整。
阿布·纳菲·伊本·扎希德(Abu Nafee)女士

4
这将提供包含已执行脚本的目录。例如,运行python3 -m topmodule.submodule.script时它将给出/path/to/topmodule/submodule而不是/path/to/topmodule
danijar '19

14

实现此目的的标准方法是使用包装中的pkg_resources模块setuptoolssetuptools用于创建可安装的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

现在,假设您要从module访问文件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

3

尝试:

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

1
这正是我所需要的。一个简单的解决方案对我有用,因为我的结构是root-> config-> conf.py,我想在conf.py中定义项目根,而root恰好是该文件的两个级别。
Daniyal Arshad


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)在项目根目录下为我工作。

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

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

import os

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

3
这不适用于anaconda或pipenv之类的设置,因为在这种情况下,虚拟环境不包含在项目中。
Gripp

1

我最近一直在尝试做类似的事情,但我发现这些答案不足以满足我的用例(需要检测项目根目录的分布式库)。主要是我一直在与不同的环境和平台作斗争,但仍然没有找到完全通用的东西。

项目本地代码

我已经看到了这个示例,并在一些地方(例如Django等)使用了该示例。

import os
print(os.path.dirname(os.path.abspath(__file__)))

如此简单,仅当代码片段所在的文件实际上是项目的一部分时才起作用。我们不检索项目目录,而是片段的目录

同样,从应用程序的入口点之外调用时,sys.modules方法会崩溃,特别是我观察到子线程无法在不与' main '模块相关的情况下确定该方法。我已将导入明确地放在一个函数中,以演示从子线程进行的导入,将其移至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。threading.main_thread()但是,在Python3.4 +中,线程库已经使用了,每个人都没有使用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上对此进行了测试


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]

这仅适用于具有蟒蛇项目固定项目结构的conda项目


0

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

例如:Project1-D:\ projects

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.