Python导入树中的顶层模块在哪里?如何定义它?
我在做一个涉及多个文件的项目,遇到了一些关于导入的麻烦。我看了大约20篇文章和Stack Overflow上的问题,但没有一篇能解释这种情况。
假设我的文件夹结构是这样的:
\package
\folder_a
__init__.py
b.py
__init__.py
a.py
在b.py
里有一个简单的hello
函数。在a.py
中,我写了from package.folder_a.b import hello
。当我运行a.py
时,出现了错误“ModuleNotFoundError: no module named 'package'”。
为什么会这样呢?当运行a.py
时,package
不是顶层目录吗?
我找到了一种解决方法,就是把a.py
中的代码改成from folder_a.b import hello
,但这样又会出现另一系列问题。假设我正在处理一个稍微复杂一点的文件结构(其实我就是),比如:
\package
\folder_a
__init__.py
b.py
\folder_b
__init__.py
c.py
__init__.py
a.py
在这里,我想把hello
导入到c.py
中。无论是from package.folder_a.b import hello
还是from folder_a.b import hello
,都会出现同样的“ModuleNotFoundError: no module named 'package/folder_a'”。我猜这是因为在执行文件时,文件没有把那些文件夹添加到命名空间中(从a.py
导入c.py
时可以正常工作)。有没有办法强制让\package
成为顶层模块,而不需要从它自己目录下的文件运行脚本呢?希望这样说你能明白。谢谢。
2 个回答
Python把你正在运行的脚本所在的文件夹当作最顶层的目录。举个例子,当你在文件夹folder_b里执行b.py时,Python会把/folder_b当作顶层目录,而不是/package。
为了处理你的情况,你可以使用相对导入。下面是如何在a.py和c.py中调整你的导入方式:
文件a.py ->
from .folder_a.b import hello
文件c.py ->
from ..folder_a.b import hello
这样做可以让Python正确找到模块的路径,无论你当前的工作目录是什么,或者你正在执行的脚本在哪里。
为了避免混淆,我们来重新命名一下package
,因为这里的意思并不是定义一个包含名为a
的模块的包。你有一个项目,这个项目定义了三样东西:
- 一个顶层包,里面有一个名为
b
的模块 - 一个顶层包,里面有一个名为
c
的模块 - 一个名为
a.py
的脚本。
或者你也可以定义两样东西:
- 一个顶层包
package
,里面有两个子包p1
和p2
- 一个名为
a.py
的脚本。
所以你的源代码目录可能看起来像这样:
\project
\p1
b.py
\p2
c.py
a.py
或者这样:
\project
\package
\p1
b.py
\p2
c.py
a.py
(除非你真的有代码要放在__init__.py
文件里,否则很久以来就不需要这些文件来定义一个包了。)
当你从一个发行包中安装代码时(比如,用pip install myproject
在虚拟环境中安装),那么a.py
会被放到一个合适的bin
目录里,而两个包p1
和p2
会被放到一个合适的site-packages
目录里。例如,假设你选择了上面的第一个选项:
$ python3 -m venv venv
$ venv/pip/install myproject
[...]
$ ls venv/bin/a.py
venv/bin/a.py
$ ls -d venv/lib/python3.12/site-packages/p*
venv/lib/python3.12/site-packages/p1
venv/lib/python3.12/site-packages/p1
所以a.py
应该把p1
和p2
当作顶层包来引用,比如这样:
from p1 import b
from p2 import c
而不是用相对导入的方式,就好像a
是和p1
、p2
在同一个包里一样。如果你选择了第二个选项:
from package.p1 import b
from package.p2 import c
如果你确实希望a.py
成为一个可以作为脚本运行的模块,那么你的源代码树可能看起来像这样:
\project
\package
\p1
b.py
\p2
c.py
a.py
现在package.a
是一个模块,连同package.p1
和package.p2
,而a.py
可以使用相对导入来引用p1
和p2
。为了确保当a.py
作为脚本执行时,相对导入也能正常工作,你需要在第一个相对导入之前添加以下代码:
if __name__ == '__main__' and __package__ is None:
__package__ = 'package'