这里似乎已经有了一些关于python 3中相对导入的问题,但在浏览了许多之后,我仍然没有找到我的问题的答案。 问题来了。

我有一个如下所示的包

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

我在test.py中有一行:

from ..A import foo

现在,我在包的文件夹,我运行

python -m test_A.test

我收到消息

"ValueError: attempted relative import beyond top-level package"

但是如果我在包的父文件夹中,例如,我运行:

cd ..
python -m package.test_A.test

一切都很好。

现在我的问题是: 当我在包的文件夹中,我运行test_A子包中的模块作为test_A。测试,根据我的理解,..A只上升了一层,仍然在包文件夹中,为什么它给出消息说在顶层包之外。导致此错误消息的确切原因是什么?


当前回答

正如最流行的答案所暗示的那样,基本上是因为你的PYTHONPATH或sys。路径包含。但不是你拿到包裹的路径。相对导入是相对于你当前的工作目录,而不是导入发生的文件;奇怪的是。

你可以通过先将相对导入更改为绝对导入来解决这个问题,然后以以下方式开始:

PYTHONPATH=/path/to/package python -m test_A.test

OR在以这种方式调用时强制python路径,因为:

使用python -m test_A。测试你正在使用__name__ == '__main__'和__file__ == '/absolute/path/to/test_A/test.py'执行test_A/test.py

这意味着在test.py中,你可以在main case条件下使用绝对导入半保护,也可以做一些一次性的Python路径操作:

from os import path
…
def main():
…
if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())

其他回答

我对这个问题的理解是这样的:

[案例1]当你开始一个绝对导入

python -m test_A.test

or

import test_A.test

or

from test_A import test

你实际上是将导入锚设置为test_A,换句话说,顶级包是test_A。当我们有test。py do from。一个导入xxx,你正在从锚转义,而Python不允许这样做。

[案例2]当你这样做的时候

python -m package.test_A.test

or

from package.test_A import test

你的锚变成了包,所以包/test_A/test.py从…导入xxx不会转义锚(仍然在包文件夹内),Python很乐意接受这一点。

简而言之:

Absolute-import改变当前锚(=重新定义什么是顶级包); 相对导入不会改变锚,而是限定在锚上。

此外,我们可以使用全限定模块名(FQMN)来检查这个问题。

检查每种情况下的FQMN:

【例2】测试。__name__ = package.test_A.test (CASE1)测试。__name__ = test_A.test

对于CASE2, an。import xxx将生成一个带有FQMN=package的新模块。Xxx,这个可以接受。

对于CASE1, ..从内部从…import xxx将跳出test_A的起始节点(锚点),这是Python不允许的。

我认为这种“相对导入”的限制是一种相当丑陋的设计,完全违背了Python的格言“简单比复杂好”。

这实际上比其他答案要简单得多。

TL;DR:直接导入A,而不是尝试相对导入。

当前工作目录不是包,除非您从另一个文件夹导入文件夹包。因此,如果您打算将包导入其他应用程序,那么包的行为将正常工作。不管用的是测试…

在不改变目录结构的情况下,只需要改变test.py导入foo.py的方式。

from A import foo

现在运行python -m test_A。test将在没有ImportError的情况下运行。

为什么会这样?

当前工作目录不是包,但它被添加到路径中。因此可以直接导入文件夹A及其内容。这是同样的原因,你可以导入任何其他包,你已经安装…它们都包含在你的路径中。

从包中。一个import foo

我认为这比

import sys
sys.path.append("..")

在我的情况下,我不得不改变: 解决方案1(更多更好,取决于当前的py文件路径。易于部署) 使用pathlib.Path.parents使代码更干净

import sys
import os
import pathlib
target_path = pathlib.Path(os.path.abspath(__file__)).parents[3]
sys.path.append(target_path)
from utils import MultiFileAllowed

解决方案2

import sys
import os
sys.path.append(os.getcwd())
from utils import MultiFileAllowed
import sys
sys.path.append("..") # Adds higher directory to python modules path.

试试这个。 为我工作。