Python打包中的相对导入

71 投票
3 回答
65420 浏览
提问于 2025-04-16 07:59

首先,我很抱歉,我知道关于相对导入的问题已经有很多了,但我就是找不到解决办法。如果可以的话,我想使用以下的目录结构:

myClass/
    __init__.py
    test/
        demo.py
        benchmark.py
        specs.py
    src/
        __init__.py
        myClass.py

现在我有几个问题:

  • 包内的测试文件应该怎么正确导入 myClass.py 呢?

  • 如果你想从外部导入这个包,假设你把 myClass 当作 libs/myClass 或 include/myClass 的子模块,该怎么做呢?

到目前为止,我还没找到一个优雅的解决方案。根据我理解的 Guido 的决定,应该可以用 from ..src import myClass 这样的方式,但这会报错:

ValueError: Attempted relative import in non-package

这看起来是因为它没有把 myClass 当作包来处理。根据 文档 的说明:

__init__.py 文件是让 Python 把目录当作包的必要条件;

看起来我缺少了一些东西来指定包的脚本在哪里,我应该使用 .pth 文件吗?

3 个回答

0

包内引用讲的是如何从 test/* 中导入 myClass。如果你想从外部导入这个包,你需要在运行导入程序之前,把它的路径添加到 PYTHONPATH 环境变量中,或者在代码中导入之前,把它加到 sys.path 列表里。

为什么 from ..src import myClass 会失败:可能是因为 src 不是一个Python包,所以你不能从那里导入。你需要像上面说的那样,把它添加到Python路径中。

28

昨晚我花了好几个小时终于找到了Python中相对导入的问题的答案!或者说,至少找到了一个简单的解决办法。解决这个问题的最好方法是从另一个模块来调用这些模块。比如说,你想让 demo.py 导入 myClass.py。在 myClass 文件夹里,作为子包的根目录,它们需要有一个文件来调用这两个模块。根据我的理解,工作目录总是被认为是 __main__,所以如果你从 demo.py 脚本测试导入,你会遇到错误。为了说明这一点:

文件夹结构:

myClass/
    main.py #arbitrary name, can be anything
    test/
        __init__.py
        demo.py
    src/
        __init__.py
        myClass.py

myClass.py:

def randomMaths(x):
    a = x * 2
    y = x * a
    return y

demo.py:

from ..src import myClass

def printer():
    print(myClass.randomMaths(42))

main.py:

import test.demo

demo.printer()

如果你在解释器中运行 demo.py,你会产生一个错误,但运行 main.py 就不会。这有点复杂,但确实有效 :D

42

ValueError: Attempted relative import in non-package

这个错误的意思是你在一个不是包的模块里尝试使用相对导入。问题出在包含这个 from ... import 语句的文件上,而不是你想要导入的文件。

所以,如果你在测试中使用相对导入,比如说,你应该让你的测试成为你包的一部分。这意味着:

  1. 在 test/ 文件夹里添加一个 __init__.py 文件。
  2. 通过一些外部脚本来运行它们,比如 nosetests。

如果你像这样运行 python myClass/test/demo.py,相对导入也不会起作用,因为你是把 demo 模块当作普通文件在运行,而不是作为包。相对导入要求使用它的模块本身也要作为包模块被导入,比如 from myClass.test.demo import blabla,或者使用相对导入。

撰写回答