这里似乎已经有了一些关于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())

其他回答

如果在上层文件夹中有__init__.py,可以将导入初始化为 导入文件/路径作为初始化文件的别名。然后你可以使用它在较低的脚本:

import alias

正如最流行的答案所暗示的那样,基本上是因为你的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())

在python2中不确定。X,但在python 3.6中,假设你试图运行整个套件,你只需要使用-t

-t,——顶级-directory目录 项目的顶级目录(默认为开始目录)

在一个结构上

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

例如,你可以用:

Python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

仍然导入my_module。My_class没有大的戏剧。

这在Python中是非常棘手的。

我将首先说明为什么你会有这个问题,然后我会提到两种可能的解决方案。

这是怎么呢

你必须考虑Python文档中的这段话:

请注意,相对导入基于当前的名称 模块。由于主模块的名称总是“main”, 用作Python应用程序主模块的模块 必须始终使用绝对导入。

还有PEP 328中的以下内容:

相对导入使用模块的name属性来确定这一点 模块在包层次结构中的位置。如果模块名是 不包含任何包信息(例如它被设置为'main') 然后相对导入被解析,就好像模块是顶级的一样 模块,而不管模块实际位于文件中的哪个位置 系统。

相对导入工作于filename (__name__属性),它可以取两个值:

它是文件名,前面是文件夹结构,中间用圆点分隔。 例如:package.test_A.test 这里Python知道父目录:在test之前是test_A,然后是package。 所以你可以使用点符号进行相对导入。

#  package.test_A/test.py
from ..A import foo

然后你可以在根目录下有一个根文件,调用test.py:

#  root.py
from package.test_A import test

当您直接运行模块(test.py)时,它将成为程序的入口点,因此__name__ == __main__。文件名没有指示目录结构,因此Python不知道如何进入目录。对于Python, test.py成为顶级脚本,在它之上没有任何东西。这就是为什么你不能使用相对导入。


可能的解决方案

A)解决这个问题的一个方法是有一个根文件(在根目录下)来调用模块/包,就像这样:

Root.py导入test.py。(入口点,__name__ == __main__)。 Test.py(相对)导入foo.py。 py表示模块已导入。

输出结果为:

package.A.foo has been imported
Module's name is:  package.test_A.test

B)如果你想将代码作为一个模块而不是一个顶级脚本来执行,你可以从命令行尝试:

python -m package.test_A.test

欢迎提出任何建议。

你还应该检查一下:相对进口,特别是BrenBarn的答案。

假设: 如果您在包目录中,则A和test_A是单独的包。

结论: ..只允许在包中导入。

进一步指出: 如果您想强制包可以放置在sys.path中的任何路径上,那么使相对导入仅在包中可用是很有用的。

编辑:

我是唯一一个认为这很疯狂的人吗?为什么当前的工作目录不被认为是一个包?——Multihunter

当前工作目录通常位于sys.path中。所有文件都是可导入的。这是自Python 2以来的行为,当时包还不存在。将运行目录作为一个包将允许以“import a”和“import a”的方式导入模块,这将是两个不同的模块。也许这是需要考虑的不一致性。