在相对导入中,超出顶级包错误

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

我有一个如下所示的包

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

我在test.py中有一行:

from ..A import foo

现在,我在package文件夹中,然后运行

python -m test_A.test

我收到消息

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

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

cd ..
python -m package.test_A.test

一切都很好。

现在我的问题是: 当我在package文件夹中,并且我在test_A子包中运行模块作为test_A.test时,根据我的理解,..A只上升了一层,仍然在package文件夹中,为什么它给出消息说beyond top-level package。导致此错误消息的确切原因是什么?< / p >

552483 次浏览
< p >假设:< br > 如果您在package目录中,Atest_A是单独的包。< / p > < p >结论:< br > ..A只允许在包中导入。< / p > 进一步指出:< p > < br > 如果您想强制包可以放置在位于sys.path的任何路径上,则只在包中使用相对导入是有用的

编辑:

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

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

import sys
sys.path.append("..") # Adds higher directory to python modules path.
< p >试试这个。

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


这是因为python不会记录包是从哪里加载的。因此,当您执行python -m test_A.test时,它基本上只是放弃了test_A.test实际上存储在package中的知识(即package不被视为一个包)。尝试from ..A import foo是试图访问它没有任何更多的信息(即加载位置的兄弟目录)。这在概念上类似于允许在math中的文件中使用from ..os import path。这很糟糕,因为您希望包是不同的。如果他们需要使用其他包中的内容,那么他们应该使用from os import path全局引用它们,并让python使用$PATH$PYTHONPATH找出它们的位置。

当您使用python -m package.test_A.test时,那么使用from ..A import foo就可以解决问题,因为它跟踪了package中的内容,并且您只是访问了加载位置的子目录。

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

# EYZ0

我认为这比

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

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


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

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

“同样可以通过编程方式指定:

导入系统

sys.path.append (' . ')

当然,上面的代码必须在另一个导入之前编写 声明。< / >强

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

如果在上层文件夹中有__init__.py,则可以将导入初始化为 import file/path as alias。然后你可以在较低的脚本上使用它:

import alias

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

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

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

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

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


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

import os
print (os.getcwd())

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

正如最流行的答案所建议的那样,基本上是因为您的PYTHONPATHsys.path包含了.,但不包含到包的路径。相对导入是相对于你当前的工作目录,而不是导入发生的文件;奇怪的是。

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

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

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

使用python -m test_A.test,您将使用__name__ == '__main__'__file__ == '/absolute/path/to/test_A/test.py'执行test_A/test.py

这意味着在test.py中,你可以在主case条件下使用绝对的import半保护,也可以做一些一次性的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

import test_A.test

from test_A import test

你实际上是将import-anchor设置为test_A,换句话说,顶级包是test_A。因此,当我们使用test.py do from ..A import xxx时,您正在从锚中转义,而Python不允许这样做。

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

python -m package.test_A.test

from package.test_A import test

你的锚变成了package,所以package/test_A/test.pyfrom ..A import xxx不会转义锚(仍然在package文件夹中),Python很乐意接受这一点。

简而言之:

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

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

检查每种情况下的FQMN:

  • [case2] # eyz0 = # eyz1
  • [case1] # eyz0 = # eyz1

因此,对于CASE2, from .. import xxx将导致FQMN=package.xxx的新模块,这是可以接受的。

而对于CASE1, from .. import xxx内的..将跳出test_A开始节点(锚),这是Python不允许的。

[2022-07-19]我认为这个“相对进口”;限制是一个相当丑陋的设计,完全违背了Python的座右铭“简单比复杂好”。

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

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

在一个结构上

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


例如,你可以用:

# EYZ0

并且仍然导入my_module.my_class,没有主要剧情。

在我的情况下,我必须更改为: 解决方案1(更多更好,取决于当前的py文件路径。易于部署) 使用# EYZ0 < / p >
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

# EYZ0。

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

这是怎么呢

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

请注意,相对导入是基于当前的名称 模块。由于主模块的名称总是"主要", 用作Python应用程序主模块的模块

还有来自PEP 328的以下内容:

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

相对导入从文件名(__name__属性)开始,它可以有两个值:

  1. 是文件名,前面是文件夹结构,中间用圆点分隔。 例如: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
  1. 当您直接运行模块(test.py)时,它将成为程序的入口点,因此__name__ == __main__。文件名没有指示目录结构,因此Python不知道如何进入目录。对于Python, test.py成为顶级脚本,在它之上没有任何东西。这就是为什么你不能使用相对导入。

可能的解决方案

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

enter image description here

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

输出结果为:

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

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

python -m package.test_A.test

欢迎提出任何建议。

你还应该检查:第10亿次相对进口,特别是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;:直接导入A,而不是尝试相对导入。

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

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

from A import foo

现在从package目录运行python -m test_A.test将在没有ImportError的情况下运行。

为什么会这样?

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

在test.py中删除..即可 对我来说,pytest很好地解决了这个问题 < / p >
示例:
from A import foo