这里似乎已经有了一些关于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只上升了一层,仍然在包文件夹中,为什么它给出消息说在顶层包之外。导致此错误消息的确切原因是什么?


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

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

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

编辑:

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

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


import sys
sys.path.append("..") # Adds higher directory to python modules path.

试试这个。 为我工作。


编辑:这个问题在其他问题中有更好/更连贯的答案:

兄弟包导入 第10亿次相对进口


Why doesn't it work? It's because python doesn't record where a package was loaded from. So when you do python -m test_A.test, it basically just discards the knowledge that test_A.test is actually stored in package (i.e. package is not considered a package). Attempting from ..A import foo is trying to access information it doesn't have any more (i.e. sibling directories of a loaded location). It's conceptually similar to allowing from ..os import path in a file in math. This would be bad because you want the packages to be distinct. If they need to use something from another package, then they should refer to them globally with from os import path and let python work out where that is with $PATH and $PYTHONPATH.

当你使用python -m package.test_A。测试,然后使用from ..import foo可以很好地解决问题,因为它跟踪了包中的内容,而你只是访问了加载位置的子目录。

为什么python不认为当前工作目录是一个包?不知道,但天哪,这将是有用的。


从包中。一个import foo

我认为这比

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

编辑:2020-05-08:似乎我引用的网站不再由写建议的人控制,所以我删除了该网站的链接。谢谢你让我知道baxx。


如果有人在已经提供的精彩答案后仍然有点纠结,我在一个网站上找到了不再可用的建议。

我提到的网站的重要引用:

“同样可以通过编程方式指定: 导入系统 sys.path.append (' . ') 当然,上面的代码必须在另一个导入之前编写 声明。

很明显,事情是这样的,事后想想。我试图在我的测试中使用sys.path.append('..'),但遇到了op发布的问题。路径定义之前我的其他导入,我能够解决这个问题。


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

import alias

这些解决方案在3.6中都不适合我,文件夹结构如下:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

我的目标是从module1导入到module2。奇怪的是,最后对我有用的是:

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

注意单点解决方案,而不是迄今为止提到的两点解决方案。


编辑:以下内容帮助我澄清了这一点:

import os
print (os.getcwd())

在我的例子中,工作目录(出乎意料地)是项目的根目录。


正如最流行的答案所暗示的那样,基本上是因为你的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的格言“简单比复杂好”。


在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没有大的戏剧。


在我的情况下,我不得不改变: 解决方案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

这在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的答案。


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

在A/__init__.py中导入foo:


from .foo import foo

当从test_A/导入A/时


import sys, os
sys.path.append(os.path.abspath('../A'))
# then import foo
import foo


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

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

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

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

from A import foo

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

为什么会这样?

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


只需移除…在test.py 对我来说,pytest很好地解决了这个问题 例子:

from A import foo