python3 中 pytest 的布局和导入

Layout and importing for pytest in python3

我在从我的 pytest 函数导入模块时遇到问题。我知道这方面有一百万个问题,但我已经阅读了很多,但我仍然无法理解。

$ tree
.
└── code
    ├── eight_puzzle.py
    ├── missionaries_and_cannibals.py
    ├── node.py
    ├── search.py
    └── test
        ├── test_eight_puzzle.py
        └── test_search.py

2 directories, 6 files
$
$ grep import code/test/test_search.py
import sys
import pytest
import code.search
$
$ pytest
...
ImportError while importing test module '~/Documents/code/test/test_search.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
code/test/test_search.py:14: in <module>
    import code.search
E   ModuleNotFoundError: No module named 'code.search'; 'code' is not a package
...

我希望它能起作用。 'code'是一个包吧? Python 3 中的包是任何包含 .py 文件的目录。

我也尝试过使用相对导入 - from .. import search - 我得到以下结果。

ImportError while importing test module '~/Documents/code/test/test_search.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
code/test/test_search.py:14: in <module>
    from .. import search
E   ImportError: attempted relative import with no known parent package

我也试过修改 sys.path,如图 ,指定我的 PYTHONPATH,并在代码和测试中添加 init.py 文件。

我可以在不使用 setuptools 的情况下使这个导入工作吗?这只是为了试验,所以我不想处理开销。

注意我正在使用 conda 可能也很重要,因为当我使用 python 2 pip 安装版本的 pytest 和 init[ 时它似乎可以工作=29=.py 文件。

关于没有 __init__.py 个文件的目录的一些注意事项:

隐式命名空间包

虽然没有 __init__.py 的目录是 Python 3 中的有效导入源,但它不是常规包,而是隐式命名空间包(请参阅 PEP 420 了解细节)。在其他属性中,隐式命名空间包在导入时是第二个 class 公民,这意味着当 Python 在 sys.path 中有两个同名的包时,一个是常规包,另一个是隐式命名空间包,无论哪个包先出现,都会优先使用常规包。自己查一下:

$ mkdir -p implicit_namespace/mypkg
$ echo -e "def spam():\n    print('spam from implicit namespace package')" > implicit_namespace/mypkg/mymod.py
$ mkdir -p regular/mypkg
$ touch regular/mypkg/__init__.py
$ echo -e "def spam():\n    print('spam from regular package')" > regular/mypkg/mymod.py
$ PYTHONPATH=implicit_namespace:regular python3 -c "from mypkg.mymod import spam; spam()"

这将打印 spam from regular package:虽然 implicit_namespacesys.path 中排在第一位,但 regular 中的 mypkg.mymod 是导入的,因为 regular/mypkg 是普通包裹。


现在你知道了,因为你的包 code 是一个隐式命名空间包,所以 Python 如果遇到一个 code 会更喜欢定期导入 code。不幸的是,there is a module code in the stdlib,所以这实际上是一个 "reverse name shadowing" 问题:您有一个与来自 stdlib 的对象同名的导入对象,但它没有隐藏 stdlib 导入,而是隐藏了您的导入。

因此,为了使布局可用,您需要做两件事:

  1. code 目录一个唯一的名称(对于这个答案的例子,让它成为 mycode
  2. 在那之后,当 运行 从项目根目录进行测试时,您仍然需要修复 sys.path,因为它本身不在 sys.path 中。你有一些可能性:
    • 添加一个空的 conftest.py 文件到根目录(除了 mycode 目录)。这将指示 pytest 将根目录添加到 sys.path(有关解释,请参阅 here)。您现在可以像往常一样 运行 pytest 导入将得到解决;
    • 运行 通过 python -m pytest 的测试 - 调用解释器直接将当前目录添加到 sys.path;
    • 通过 PYTHONPATH 环境变量将当前目录添加到 sys.path,例如运行 PYTHONPATH=. pytest.