获取根项目结构的路径

我有一个python项目,在项目根中有一个配置文件。 在整个项目中,需要在几个不同的文件中访问配置文件。< / p > 所以它看起来像:<ROOT>/configuration.conf <ROOT>/A/a.py<ROOT>/A/B/b.py(当b,a.py访问配置文件时)。< / p >

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

411717 次浏览

你可以像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轻松访问常量(在任何其他文件中)。

要获得“根”模块的路径,你可以使用:

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获取所需文件在系统上的实际路径。

假设你有一个名为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)和你当前正在处理的模块中的文件。

我使用一个标准的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])

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

项目本地代码

我在一些地方看到过这个例子,并使用了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(). conf中。在这种情况下,我们抛出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上进行了测试

其他回答建议在项目的顶层使用文件。如果你使用pathlib.Pathparent (Python 3.4及以上版本),这是不必要的。考虑下面的目录结构,其中除了README.mdutils.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和导入(重构工具可用于自动化此操作)。

试一试:

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

之前所有的解决方案对于我认为你需要的东西来说似乎都过于复杂,而且通常对我不起作用。下面的单行命令就是你想要的:

import os
ROOT_DIR = os.path.abspath(os.curdir)
我也在这个问题上苦苦挣扎,直到我找到了这个解决方案。 在我看来,这是最干净的解决办法

setup . py中添加“packages”

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

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

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

/home/user/project_root / bin / python / env /违约

# 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

这里有很多答案,但我找不到一个简单的涵盖所有情况的答案,所以请允许我提出我的解决方案:

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

只是一个例子:我想从< em > helper1.py < / em >中运行< em > runio.py < / em >

项目树示例:

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)

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

< p >的例子: Project1—D:\projects

src

ConfigurationFiles

Configuration.cfg

路径= " . . / src / ConfigurationFiles / Configuration.cfg”

重要的:此解决方案要求您将文件作为带有python -m pkg.file的模块运行,而不是像python file.py那样作为脚本运行。

import sys
import os.path as op
root_pkg_dirname = op.dirname(sys.modules[__name__.partition('.')[0]].__file__)

其他答案有一些要求,比如依赖于环境变量或包结构中另一个模块的位置。

只要你以python -m pkg.file的身份运行脚本(带有-m),这种方法就是自包含的,并且可以在包的任何模块中工作,包括在顶层的__init__.py文件中。

import sys
import os.path as op


root_pkg_name, _, _ = __name__.partition('.')
root_pkg_module = sys.modules[root_pkg_name]
root_pkg_dirname = op.dirname(root_pkg_module.__file__)


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

它的工作原理是,取__name__中包含的虚线字符串中的第一个组件,并将其作为sys.modules中的键,返回顶层包的模块对象。它的__file__属性包含了我们使用os.path.dirname()修剪/__init__.py后想要的路径。

返回到项目根目录的路径

import sys
print(sys.path[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
我自己的决定是这样的。
需要从主文件中获取“MyProject/drivers”的路径
MyProject/
├─── RootPackge/
│    ├── __init__.py
│    ├── main.py
│    └── definitions.py
│
├─── drivers/
│    └── geckodriver.exe
│
├── requirements.txt
└── setup.py
< p > definitions.py
不要放在项目的根目录中,而是放在主包的根目录

from pathlib import Path


ROOT_DIR = Path(__file__).parent.parent

使用ROOT_DIR:
main.py < / p >

# 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

工作在命令行从rootpackage:

$ cd RootPackge
$ py main.py

从项目上面的目录工作:

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

如果你给主文件一个绝对路径,从任何地方工作。
不依赖于venv。

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

我有一个简单的用例困扰了我一段时间。尝试了一些解决方案,但我觉得它们都不够灵活。

这就是我想出来的。

    在根目录下创建一个空白的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文件某种程度上保证了它不会因为将来的一些重构而被移动到其他地方。

干杯

这里有一个包可以解决这个问题:在根

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

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

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

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/

这并不是这个问题的确切答案;但它可能会帮助某些人。事实上,如果您知道文件夹的名称,就可以这样做。

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

简单而动态!

此解决方案适用于任何操作系统和任何级别的目录:

假设你的项目文件夹名为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]


一行的解决方案

嗨!我一直都有这个问题,而且没有一个解决方案对我有效,所以我使用了here::here()R中使用的类似方法。

  1. 安装groo

  2. 在你的根目录中放置一个隐藏文件,例如.my_hidden_root_file

  3. 然后从目录层次结构中的任意较低的(即在

    .执行以下命令

from groo.groo import get_root
root_folder = get_root(".my_hidden_root_file")


  1. 就是这样!

它只执行以下函数:

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

项目根目录没有__init__.py。 我通过寻找一个没有__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)