首页 > 解决方案 > 我可以使用 python 模块的 main 进行测试吗?

问题描述

我正在 Python 3.8.2 上开发一个 Python 库,并且我想将一个模块作为主要模块运行以进行测试。当我尝试时,我收到 ModuleNotFound 错误。

这是我的库结构:

.
├── foo
│   ├── __init__.py
│   ├── bar.py
│   └── quux
│       ├── __init__.py
│       ├── corge.py
│       └── garply.py
├── main.py

巴里.py:

def baz():
    print("baz")

corge.py

from foo.quux.garply import *


def grault():
    waldo()
    print("grault")


if __name__ == '__main__':
    grault()

garply.py

def waldo():
    print("waldo")

主文件

from foo.bar import *
from foo.quux.corge import *

if __name__ == '__main__':
    baz()
    grault()

(所有__init__.py文件都是空的)

当我运行时main.py,它可以工作。

$ python main.py
baz
waldo
grault

如果我尝试运行corge.py,我会收到以下错误:

$ python foo/quux/corge.py 
Traceback (most recent call last):
  File "foo/quux/corge.py", line 1, in <module>
    from foo.quux.garply import *
ModuleNotFoundError: No module named 'foo'

我当前的工作目录是什么并不重要,它总是会给出这个错误。

$ cd foo/quux/
$ python corge.py 
Traceback (most recent call last):
  File "corge.py", line 1, in <module>
    from foo.quux.garply import *
ModuleNotFoundError: No module named 'foo'

在我对此进行测试时,我使用 PyCharm 2020.1 创建了一个新的 PyCharm 项目并实现了我描述的结构。令我惊讶的是,它适用于默认检测到的运行配置。

我尝试使用 PyCharm 自动创建的 venv,但它仍然不起作用。如果我直接复制/粘贴命令并使用他们的 CWD,它将不起作用。它不适用于 PyCharm 中内置的终端。它适用于 PyCharm 运行按钮。

我的模块结构有问题吗?如果是这样,PyCharm 可以做些什么来完成这项工作?如果不是,为什么它不能在 PyCharm 之外工作?

标签: pythonpython-3.xpycharmpython-module

解决方案


Python 需要知道所在的目录foo才能导入它。 sys.path列出 python 将搜索的目录。

当您安装一个包时,安装程​​序会担心这样做——通常是将模块放在一个众所周知的目录中或将安装包路径添加到sys.path.

当你运行一个脚本时,python 会自动将该脚本的路径添加到sys.path,所以当你运行时main.py,你会发现foo.

如何作为模块运行__main__

一种选择是使软件包可安装(setup.py、wheels 等)并使用开发模式(“pip install --editable ./”与“python setup.py develop”进行一些讨论)。这就是我在开发我计划提供给其他人的东西时所做的。

另一种方法是将您的目录添加到 PYTHONPATH,甚至可能在您运行程序时。在Linux上,那将是

PYTHONPATH=path/to/fooproject:$PYTHONPATH python foo/quux/corge.py

还有一个,我也这样做,是破解模块本身的路径。 __file__给出相对于当前工作目录的文件名,并且您知道自己在包层次结构中的深度。所以你可以只做__file__绝对并剥离几个目录名称

corge.py

import sys
import os

if __name__ == "__main__":
    # I'm two levels deep in the package so package directory is
    packagedir = os.path.abspath(os.path.join(os.path.dirname(__file__),
        "..", ".."))
    sys.path.insert(0, packagedir)
    import foo

最后,不要一开始就这样做。当您corge.py作为脚本运行时,它获得的命名空间与作为模块导入的命名空间__main__不同foo.bar.corge。它的全局变量/类/函数被加载了两次,你会得到不同的变量,这取决于你是通过__main__命名空间还是foo.bar.corge.

最好将您想要放入 main in 的任何内容corge.py,使它们成为单独的脚本。例如,您可以添加def main()到您的模块中。您可以在其中main.py添加一个选项--run foo.bar.corge,告诉 main 导入corge.py并运行其main(). argparse子命令可用于此。


推荐阅读