为什么 pytest 总是说“ImportError:在没有已知父包的情况下尝试相对导入”

Jac*_*ang 8 python import pytest

我是 pytest 和 python 的新手。我的项目结构是:

projroot/
|-src/
  |-a.py
|-test/
  |-test_a.py
Run Code Online (Sandbox Code Playgroud)

而 test_a.py 是:

projroot/
|-src/
  |-a.py
|-test/
  |-test_a.py
Run Code Online (Sandbox Code Playgroud)

然后在pytest以下运行projroot

projroot>pytest
Run Code Online (Sandbox Code Playgroud)

并且总是有错误信息:E ImportError: attempted relative import with no known parent package。Python 版本:Windows 10 x64 上的 3.8。我阅读了很多文章和博客以及官方建议,但仍然没有弄清楚。请帮帮我!

Han*_* Qi 8

添加__init__.py到项目根目录实际上弄乱了我的 pytest 发现以及 pylint (导致Unable to import \'angles\' pylint(import-error))。我必须将其从项目根目录中删除,然后仅将其添加到我的测试文件夹中,这解决了我的 pytest 发现和 pylint 警告。

\n

在项目根目录中__init__.py,我将拥有ModuleNotFoundError: No module named \'angles\'

\n

下面是我的文件夹结构,其中 pytest discovery 和 pylint (没有警告)都可以工作:

\n
powerfulpython\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 angles.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 tests\n    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 test_angles.py\n
Run Code Online (Sandbox Code Playgroud)\n

在 Angles.py 中,我Class Angle尝试使用from angles import Angleinside进行绝对导入test_angles.py

\n

在我发现问题是__init__.py在项目根目录中存在不必要的问题之前。我也尝试过

\n
    \n
  1. 编辑python.testing.cwd
  2. \n
  3. 设置 pytest 的 \'--rootdir\'
  4. \n
  5. 将 Python 扩展降级到 2020.9.114305(以下https://github.com/microsoft/vscode-python/issues/14579
  6. \n
\n

他们都没有工作。

\n

在我删除__init__.py项目根目录之前,在测试 pytest 时,所做的python -m pytest工作是有效的,因为项目根目录会自动添加到 sys.path 之前,因此绝对导入可以从根目录正常工作,并且导入语句可以angles在根目录下找到。执行pytest(不使用 python -m)会失败,因为项目根目录未添加到 sys.path 之前。后者可以通过手动将根目录添加到$PYTHONPATH来临时解决。然而,这是一个不可持续的解决方案,因为我不想将我想要测试的每个新项目作为 PYTHONPATH 编辑添加到我的 ~/.zshrc 中。

\n

我尝试过的另一个修复了这两个问题pytest并测试发现的方法是在代码中插入sys.path.insert(0,\'/Users/hanqi/Documents/Python/powerfulpython\') ,但这太丑陋了。我不想只是为了测试而向代码添加行,然后必须稍后删除它们。

\n

在完成所有这些之后,我删除了__init__.py根目录,一切都很好。\n不需要上面列出的 3 项中的任何一项。

\n

我经历过的许多解决方案都涉及查找测试文件夹,但这不是我的问题。我的测试在其文件夹中正确找到,只是正在from angles import Angle破坏导致测试发现失败。

\n

__init__.py研究项目根目录如何破坏事物:

\n

我打印了 sys.path 并用它实现,~/Documents/Python将其添加到 sys.path 的前面,如果没有它,~/Documents/Python/powerfulpython则将其添加到前面。后面的路径正是我的项目根目录。\n我猜这与https://docs.pytest.org/en/6.2.x/pythonpath.html( (默认))
有关。\n我希望看到内部导致被预先考虑和项目中的根本原因要预先考虑,但奇怪的是,当我把两者都留在里面时,我才看到后者 。--import-mode=prepend__init__.pytests~/Documents/Python/powerfulpython__init__.py~/Documents/Python__init__.py

\n

我还尝试删除__init__.py内部tests(但让__init__.pyroot 保留),并查看~Documents/Python/powerfulpython/testssys.path 前面的内容。如果我在根目录和测试中都删除,也会在前面添加同一行__init__.py,不知道为什么。

\n

编辑: \n上述行为在https://docs.pytest.org/en/6.2.x/pythonpath.html中进行了解释

\n

然后它会向上搜索,直到找到最后一个仍包含__init__.py文件的文件夹,以便找到包根目录

\n

在使用python -m pytest -s(-s 所以成功的测试运行不会吞掉我的print(sys.path)) 而不是仅仅重复我的上述实验之后pytest,我确认有 2 个参与者在 sys.path 前面。

\n
    \n
  1. python -m(它前面的内容取决于命令运行的目录)
  2. \n
  3. pytest(它前面的内容取决于__init__.py插入的位置(在上面链接的 pytest 文档中进行了解释)
  4. \n
\n

两者都具有编辑 sys.path 的效果,有助于避免导入失败

\n

上面的 Jacques Yang 需要 2__init__.py将其 projroot 的包含文件夹放入 sys.path 中,因此在他的projroot.test值中显示为,并且该模块名称中的 2 个点 ( ) 允许两个点相对导入。他也可以使用绝对导入的.__package__test_a.py__name__projroot.test.test_a__name__from ..src import afrom projroot.src import a

\n

如果他没有__init__.py在 projroot 中添加 ,则__package__将会变为 just ,test并且__name__将是test.test_a,这将只允许最多 1 步的向上相对步长。(这个步进概念也被 BrenBarn 在《相对导入》中第十亿次解释过)。

\n

当存在但不存在__init__.py时,那就是,因为是并且是testprojrootValueError: attempted relative import beyond top-level package__package__test__name__test.test_a允许后退 1 步,但编码后退 2 步。\n标题中的问题出现在 甚至不存在 时__init__.pytest因此__package__完全空白,并且__name__只是test_a文件本身)

\n

我在想为什么OP需要2__init__.py而我只需要 1,而 2 实际上损害了我的尝试。然后我意识到 OP 正在执行相对导入,而我正在使用 进行绝对导入from angles import Angle。如果我遵循OP的模式并进行相对导入from ..angles import Angle,那么我还需要添加__init__.pyunder powerfulpython,否则我会得到相同的错误。

\n

那么为什么绝对导入会与 extra __init__.pyin发生冲突呢~/Documents/Python/powerfulpython?因为这__init__.py会导致 pytest 在层次结构中上升到更高的位置并前置~/Documents/Python到 sys.path,但我需要绝对导入~/Documents/Python/powerfulpython(这可以通过运行 using 来解决python -m pytest,如上面前置部分的 2 个角色中所述,但感觉有点不自然)简单地pytest

\n

我有一个关于导入概念的 30 分钟演示视频:https://towardsdatascience.com/taming-the-python-import-system-fbee2bf0a1e4,可以帮助人们理解__package____name__.

\n


MMS*_*MMS 7

通过在根目录中包含 setup.py 文件并在每个对我有用的文件夹中包含 init.py ,并且要在任何文件中导入包,只需从父目录中给出该包位置

安装程序.py

from setuptools import setup, find_packages

setup(name="PACKAGENAME", packages=find_packages())
Run Code Online (Sandbox Code Playgroud)
projroot/
|-__init__.py
|-setup.py
|-src/
  |-a.py
  |-__init__.py
|-test/
  |-test_a.py
  |-__init__.py
Run Code Online (Sandbox Code Playgroud)
from src import a
def test_a():
  pass
Run Code Online (Sandbox Code Playgroud)

参考 https://docs.pytest.org/en/6.2.x/goodpractices.html


Jac*_*ang 6

我发现我必须补充__init__.pytestprojroot__init__.pyinsrc是没有必要的。并且pytest可以在 3 个文件夹中正确运行:

# parent folder of project root
C:\>pytest projroot

# project root
C:\>cd projroot
C:\projroot>pytest

# test folder
C:\projroot>cd test
C:\projroot\test>pytest
Run Code Online (Sandbox Code Playgroud)