我正在Python 3.8.2上开发Python库,我想将模块作为主要模块运行以进行测试。尝试时,出现ModuleNotFound错误。
这是我的图书馆结构:
.
├── foo
│ ├── __init__.py
│ ├── bar.py
│ └── quux
│ ├── __init__.py
│ ├── corge.py
│ └── garply.py
├── main.py
bary.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")
main.py
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中的终端。它only与PyCharm Run按钮一起使用。
我的模块结构有问题吗?如果是这样,PyCharm可以做些什么?如果没有,为什么它在PyCharm之外不起作用?
您对main.py
的调用是有效的,因为Python会在模块的直接子目录中查找。任何其他位置要导入foo.yadda.whatever
的模块都必须通过搜索foo
来找到PYTHONPATH
。因此,您需要将foo
的父目录添加到PYTHONPATH
。
Python需要知道foo
所在的目录才能将其导入。 sys.path
列出了python搜索的目录。
[安装软件包时,安装程序担心这样做-通常将模块放置在知名目录中或将安装软件包路径添加到sys.path
。
[运行脚本时,python会自动将该脚本的路径添加到sys.path
,因此,当您运行main.py
时,会发现foo
。
如何作为__main__
的模块运行
一种选择是使软件包可安装(setup.py,wheels等。)并使用开发模式(进行某些讨论的是"pip install --editable ./" vs "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
调用它们而获得不同的变量。
最好将您想放入corge.py
中的任何内容放入主脚本中,使它们成为单独的脚本。例如,您可以将def main()
添加到模块中。在main.py
中,您可以添加一个选项--run foo.bar.corge
告诉main导入corge.py
并运行其main()
。 argparse
subcommands可以用于此。