理解Python中的导入链条

5 投票
4 回答
10398 浏览
提问于 2025-04-16 13:12

我知道有很多类似的问题,但我还是搞不懂我遇到的错误,查阅文档和类似的问题也没有帮助。如果说有什么帮助的话,类似的问题让我觉得我做的没错。

我有以下几个文件:

src/main.py

from pack import pack

if __name__ == '__main__':
    pack.exec("Hello Universe!")

src/pack/pack.py

import util

def exec(text):
    util.write(text)

if __name__ == '__main__':
    exec("Hello World!")

src/pack/util.py

def write(text):
    print(text)

*src/pack/_init_.py*

EMPTY FILE

当我在 src/pack 目录下运行 python pack.py 时,它正常工作(打印“Hello World!”)。但是当我在 src 目录下运行 python main.py 时,我遇到了以下异常:

Traceback (most recent call last):
  File ".../src/main.py", line 1, in <module>
    from pack import pack
  File ".../src/pack/pack.py", line 1, in <module>
    import util
ImportError: No module named util

如果我按照建议把 pack.py 中的导入行改成 from . import util,那么情况就完全相反了。此时 main.py 可以成功运行,但 pack.py 却失败了,出现了:

Traceback (most recent call last):
  File ".../src/pack/pack.py", line 1, in <module>
    from . import util
ValueError: Attempted relative import in non-package

我本以为导入是相对于当前的位置的,因此应该可以像这样构建导入链。对于我来说,模块在不同的启动位置导入同一个兄弟文件的方式感觉很奇怪。

有人能解释一下为什么会出现这种错误吗?有没有什么方法可以让这个文件结构无论是从 main.py 还是 pack.py 运行都能正常工作?

4 个回答

2

你的包目录里缺少一个叫 __init__.py 的文件。

在你的包目录里创建一个空文件,名字就叫 __init__.py。

4

你给的链接是关于Python 2.7的文档,但看起来你在用的是Python 3.x。

你可以在这里查看正确的文档:http://docs.python.org/py3k/

在Python 3中,隐式相对导入被移除了。也就是说,你不能再用以前的方式在同一个模块里导入包了。

你需要使用 from . import util,这是一种明确的相对导入,是被允许的。而 import X 不再检查当前目录。它只会检查在sys.path里的条目。这些条目包括了脚本启动时所在的目录和Python的标准库。

6

在这两种情况下,你都会遇到导入模块的问题。这是因为在一种情况下,你是把 pack.py 当作主文件来运行,而在另一种情况下,你是把它作为一个包的一部分来运行。

当你把它当作独立脚本运行时,比如用 python pack.py,"pack" 这个文件夹会被加入到 PYTHONPATH 中,这样你就可以导入里面的任何模块了。因此,import util 就可以正常工作。

但是当你运行 python main.py 时,你是把 src 目录加入到 PYTHONPATH 中。这意味着 src 目录里的任何模块或包,比如 pack 文件夹,现在都可以被导入了。所以你可以用 from pack import pack。不过,要访问 util.py,你现在需要用 from pack import util。你也可以在 pack.py 中使用 from . import util,正如你注意到的那样。

但是你不能同时做到这两点。要么 src/ 是主目录,要么 src/pack 是主目录。

一个明显但错误的解决方案是让 main.py 把 src/pack 目录加入到 PYTHONPATH 中。这样是可以的,但并不是个好主意。正确的做法是要下定决心。src/pack 是一个应该通过 import pack 导入的模块,还是仅仅是一个装了一堆 Python 脚本的文件夹?想清楚!:-)

在这种情况下,很明显 src/pack 应该是一个模块。所以就把它当作模块来对待,并确保它像模块一样可用。这样即使在运行 pack.py 作为主脚本时,你也可以用 from pack import util

那怎么做呢?基本上,你要么把 pack 模块安装到你的 site-packages 里,要么把 src 目录加入到 PYTHONPATH 中。后者是在开发过程中你想要的做法。你可以手动用 export PYTHONPATH=<path> 来设置,或者让你的测试运行器为你处理。你没有测试运行器?那你应该有一个,不过那是另一个话题。:)

如果你不再进行开发,想要永久安装,可以看看 Distribute。它里面包含了一个测试运行器。;)

撰写回答