ChatGPT解决这个技术问题 Extra ChatGPT

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

似乎这里已经有很多关于 python 3 中的相对导入的问题,但是在经历了许多问题之后,我仍然没有找到我的问题的答案。所以这就是问题所在。

我有一个如下所示的包

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。导致此错误消息的确切原因是什么?

我这里有个想法,所以当 test_A.test 作为模块运行时,'..' 到 test_A 上面,这已经是导入 test_A.test 的最高级别了,我认为包级别不是目录级别,而是多少您导入包的级别。
我保证看完这个答案 stackoverflow.com/a/14132912/8682868 后,您将了解有关相对导入的一切。
有没有办法避免进行相对进口?比如 Eclipse 中的 PyDev 如何查看 /src 中的所有包?
您的工作目录是否也有 init.py?

M
Multihunter

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

兄弟包导入

十亿次相对进口

为什么不起作用?这是因为 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不认为当前工作目录是一个包?没有线索,但天哪,它会很有用。


我已经编辑了我的答案,以引用一个更好的答案来回答一个相同的问题。只有解决方法。我实际看到的唯一工作是 OP 所做的,即使用 -m 标志并从上面的目录运行。
需要注意的是,Multihunter 给出的链接中的 this answer 不涉及 sys.path hack,而是使用 setuptools,我认为这更有趣。
所以相对导入中的“..”不是我们通常在os的上下文中想到的“..”?在 os 中,.. 只是意味着跳出当前目录并移动到父目录,这在 python 的相对导入中不是这种情况。正如您所提到的,解释丢弃了当前工作目录的包信息。对我来说,作为一个习惯于操作系统路径概念的开发人员,这有点不直观:-(
“为什么 python 不将当前工作目录视为一个包?没有线索,但天哪,它会很有用。”而不是这样,我希望能够结合 -m 选项指定指定包的包根路径。
P
Pang
import sys
sys.path.append("..") # Adds higher directory to python modules path.

尝试这个。为我工作。


嗯......这将如何工作?每个测试文件都会有这个?
添加 sys.path.append("..") 后,我必须从“from ..A import...”中删除 ..
如果脚本是从它存在的目录之外执行的,这将不起作用。相反,您必须将此答案调整为 specify absolute path of the said script
这是最好的,最简单的选择
@AlexR,我宁愿说这是最简单的短期解决方案,但肯定不是最好的。
S
Solomon Ucko

假设:
如果您在 package 目录中,则 Atest_A 是单独的包。

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

进一步说明:
如果您想强制将包放置在位于 sys.path 上的任何路径上,则使相对导入仅在包中可用是有用的。

编辑:

只有我一个人认为这很疯狂!?为什么当前的工作目录不被认为是一个包? - 多猎人

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


只有我一个人认为这很疯狂!?为什么在世界上不将运行目录视为一个包?
这不仅是疯狂的,而且是无益的......那么你如何运行测试呢?很明显,OP 所问的是为什么我敢肯定很多人也在这里。
运行目录通常位于 sys.path 中。因此,那里的所有文件都是可导入的。这是自 Python 2 以来包尚不存在时的行为。 - 编辑答案。
我不遵循不一致之处。 python -m package.test_A.test 的行为似乎符合我们的要求,我的论点是这应该是默认值。那么,你能给我举一个这种不一致的例子吗?
我实际上在想,是否对此有功能要求?这确实很疯狂。 C/C++ 风格 #include 会非常有用!
J
Jason DeMorrow

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

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

我的目标是从模块 1 导入模块 2。最终对我有用的是,奇怪的是:

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

请注意单点,而不是到目前为止提到的两点解决方案。

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

import os
print (os.getcwd())

就我而言,工作目录(出乎意料地)是项目的根目录。


它在本地工作,但不适用于 aws ec2 实例,这有什么意义吗?
这也适用于我——在我的情况下,工作目录同样是项目根目录。我正在使用编程编辑器(TextMate)中的运行快捷方式
@thebeancounter 一样!在我的 mac 上本地工作,但在 ec2 上不起作用,然后我意识到我在 ec2 的子目录中运行命令并在本地以 root 运行它。一旦我在 ec2 上从 root 运行它,它就可以工作了。
sys.path.append(".") 有效,因为您在父目录中调用它,请注意 . 始终代表您在其中运行 python 命令的目录。
这被称为“巧合编程”,这绝对是可怕的。不要仅仅因为代码有效而做你不理解的事情。这个答案有这么多票,真是太可怕了。
G
Guzman Ojero

这在 Python 中非常棘手。

我将首先评论您为什么会遇到这个问题,然后我会提到两种可能的解决方案。

这是怎么回事?

您必须考虑 Python documentation 中的这一段:

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

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

相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为'main'),那么无论模块实际位于文件系统上的哪个位置,都将相对导入解析为就好像该模块是顶级模块一样。

相对导入从文件名(__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)解决这个问题的一种方法是有一个调用模块/包的根文件(在根目录中),如下所示:

https://i.stack.imgur.com/0Vyhc.jpg

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

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

python -m package.test_A.test

欢迎任何建议。

您还应该检查: Relative imports for the billionth time ,特别是 BrenBarn 的答案。


J
Joe Zhow

from package.A import foo

我觉得比

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

它肯定更具可读性,但仍需要 sys.path.append("..")。在 python 3.6 上测试
与旧答案相同
像这样切换到绝对导入并不能解决问题。它只会导致不同类型的 ImportError
d
dlamblin

正如最受欢迎的答案所暗示的那样,基本上是因为您的 PYTHONPATHsys.path 包含 . 但不包含您的包裹路径。并且相对导入是相对于您当前的工作目录,而不是发生导入的文件;奇怪。

您可以通过首先将相对导入更改为绝对导入,然后以以下方式启动它来解决此问题:

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

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

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

这意味着在 test.py 中,您可以在主要情况下使用绝对 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())

“并且相对导入是相对于您当前的工作目录,而不是导入发生的文件”这是不正确的。相对导入与导入发生的位置相关 - 这就是错误消息如 OP 所述的原因。在错误情况下,top-level packagetest_A,并且相对导入尝试遍历父 package 目录,从文件开始,而不是当前工作目录
M
Mierpo

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

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

我提到的网站的基本报价:

“同样可以通过这种方式以编程方式指定:import sys sys.path.append('..') 当然上面的代码必须写在另一个 import 语句之前。

很明显,它必须是这样的,事后考虑。我试图在我的测试中使用 sys.path.append('..') ,但遇到了 OP 发布的问题。通过在我的其他导入之前添加导入和 sys.path 定义,我能够解决问题。


C
Chris Collett

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

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

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

无需更改目录结构中的任何内容,只需更改 test.py 导入 foo.py 的方式。

from A import foo

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

为什么这行得通?

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


这只是偶然的,对于尝试从 package/ 开始的特定情况(即仅在 intended 包根内部,而不是仅在外部)。
M
Michael

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

import alias

__init__.py 与问题无关
J
Jerome

就我而言,我不得不更改为:解决方案 1(更好,取决于当前的 py 文件路径。易于部署)使用 pathlib.Path.parents make code cleaner

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

J
Jimm Chen

以我的拙见,我是这样理解这个问题的:

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

python -m test_A.test

或者

import test_A.test

或者

from test_A import test

您实际上将 import-anchor 设置为 test_A,换句话说,顶级包是 test_A 。因此,当我们让 test.py 执行 from ..A import xxx 时,您正在逃离锚点,而 Python 不允许这样做。

[案例 2] 当你这样做时

python -m package.test_A.test

或者

from package.test_A import test

您的锚点变为 package,因此 package/test_A/test.py 执行 from ..A import xxx 不会逃脱锚点(仍在 package 文件夹中),Python 很乐意接受这一点。

简而言之:

Absolute-import 改变当前锚点(=重新定义什么是顶级包);

相对导入不会更改锚点,但仅限于它。

此外,我们可以使用 full-qualified module name(FQMN) 来检查这个问题。

在每种情况下检查 FQMN:

[CASE2] test.__name__ = package.test_A.test

[CASE1] test.__name__ = test_A.test

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

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

[2022-07-19] 我认为这种“相对导入”限制是一个非常丑陋的设计,完全违背了 Python 的座右铭“简单胜于复杂”。


这比它需要的要复杂得多。对于 Python 的 Zen 来说就这么多。
A
Andre de Miranda

在 python 2.x 中不确定,但在 python 3.6 中,假设您尝试运行整个套件,您只需要使用 -t

-t, --top-level-directory 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


R
Rex Mudanya

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


F
Frederico Fiuza

只需删除 test.py 中的 .. 对我来说 pytest 就可以很好地使用该
示例:

from A import foo

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅