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

she*_*per 238 python import package

看来这里已经有一些关于python 3中相对导入的问题了,但是在经历了很多这些问题后,我仍然找不到我的问题的答案.所以这是问题所在.

我有一个如下所示的包裹

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py
Run Code Online (Sandbox Code Playgroud)

我在test.py中有一行:

from ..A import foo
Run Code Online (Sandbox Code Playgroud)

现在,我在文件夹中package,我跑了

python -m test_A.test
Run Code Online (Sandbox Code Playgroud)

我收到了消息

"ValueError: attempted relative import beyond top-level package"
Run Code Online (Sandbox Code Playgroud)

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

cd ..
python -m package.test_A.test
Run Code Online (Sandbox Code Playgroud)

一切都好.

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

Mul*_*ter 151

这个问题在这个问题中有一个更连贯的答案:兄弟姐妹包导入

为什么不起作用?这是因为python不记录从中加载包的位置.因此,当您这样做时python -m test_A.test,它基本上只丢弃test_A.test实际存储的知识package(即package不被视为包).尝试from ..A import foo尝试访问它不再拥有的信息(即加载位置的兄弟目录).它在概念上类似于允许from ..os import path在文件中math.这会很糟糕,因为你希望包是不同的.如果他们需要使用来自另一个包的东西,那么他们应该全局引用它们from os import path并让python在其中使用$PATH$PYTHONPATH.

当你使用时python -m package.test_A.test,然后使用from ..A import foo解析就好了,因为它跟踪了什么package,你只是访问加载位置的子目录.

为什么python不认为当前的工作目录是一个包? 没有CLUE,但天哪它会有用.

  • 我已经对答案进行了编辑,以指的是对同一个问题的更好回答。只有解决方法。我真正看到的唯一工作就是OP所做的事情,它使用`-m`标志并从上面的目录运行。 (2认同)
  • 需要注意的是,[这个答案](/sf/answers/3513576111/),来自Multihunter给出的链接,不涉及`sys.path` hack,而是使用[setuptools] (https://pypi.org/project/setuptools/),在我看来这更有趣。 (2认同)
  • “为什么 python 不认为当前工作目录是一个包?没有任何线索,但天哪,这很有用。” 相反,我希望能够与“-m”选项一起指定指定包的包根路径。 (2认同)

小智 122

import sys
sys.path.append("..") # Adds higher directory to python modules path.
Run Code Online (Sandbox Code Playgroud)

试试这个.为我工作.

  • 嗯......这工作怎么样?每个测试文件都有这个吗? (8认同)
  • 添加sys.path.append(“ ..”)后,我不得不从“ ..A import ...”中删除.. (5认同)
  • @AlexR,我宁愿说这是最简单的短期解决方案,但肯定不是最好的。 (4认同)
  • 如果脚本是从它存在的目录之外执行的,这将不起作用。相反,您必须调整此答案以[指定所述脚本的绝对路径](/sf/answers/1343348681/)。 (3认同)
  • 这是最好、最简单的选择 (2认同)

Use*_*ser 41

假设:
如果您在package目录中,A并且test_A是单独的包.

结论:
..A只允许在包中进口.

附加说明:
如果要强制将包放在位于其上的任何路径上,则仅在包中提供相对导入非常有用sys.path.

编辑:

我是唯一一个认为这是疯了的人!?为什么世界上当前的工作目录不被认为是一个包?- 多猎人

当前工作目录通常位于sys.path中.所以,那里的所有文件都是可导入的.这是自Python 2以来尚未存在的行为.使运行目录成为一个包将允许将模块导入为"import .A"和"import A",然后将导入两个不同的模块.也许这是一个不一致的考虑因素.

  • 我是唯一一个认为这是疯了的人!?为什么世界上运行目录不被认为是一个包? (71认同)
  • 这不仅是疯狂的,这是无益的......那么你如何进行测试呢?*显然OP正在询问*以及为什么我确信很多人也在这里. (5认同)

Guz*_*ero 25

这在 Python 中非常棘手

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

这是怎么回事?

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

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

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

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

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

  1. 它是文件名,前面是文件夹结构,用点分隔。对于例如:package.test_A.test 这里Python知道的父目录:之前test说到test_A,然后package因此,您可以使用点表示法进行相对导入。
#  package.test_A/test.py
from ..A import foo
Run Code Online (Sandbox Code Playgroud)

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

#  root.py
from package.test_A import test
Run Code Online (Sandbox Code Playgroud)
  1. 当您test.py直接运行模块 ( ) 时,它成为程序的入口点,因此__name__== __main__。文件名没有说明目录结构,因此 Python 不知道如何在目录中上移。对于 Python 来说,test.py成为顶级脚本,在它之上没有任何东西。这就是您不能使用相对导入的原因。

可能的解决方案

A)解决此问题的一种方法是拥有一个调用模块/包的根文件(在根目录中),如下所示:

在此处输入图片说明

  • 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
Run Code Online (Sandbox Code Playgroud)

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

python -m package.test_A.test
Run Code Online (Sandbox Code Playgroud)

欢迎任何建议。

您还应该检查:第 10 亿次的相对进口,特别是 BrenBarn 的回答。


Jas*_*row 20

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

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py
Run Code Online (Sandbox Code Playgroud)

我的目标是从module1导入module2。最终对我有用的是:

import sys
sys.path.append(".")
Run Code Online (Sandbox Code Playgroud)

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


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

import os
print (os.getcwd())
Run Code Online (Sandbox Code Playgroud)

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

  • 它在本地工作,但不适用于AWS ec2实例,这有意义吗? (2认同)
  • 这就是所谓的“巧合编程”,这绝对是糟糕的。不要仅仅因为代码可以运行就做你不理解的事情。这个答案有这么多票真是太可怕了。 (2认同)

Chr*_*ett 13

这实际上比其他答案所描述的要简单得多。

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

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

在不更改目录结构中的任何内容的情况下,需要更改的只是test.py导入foo.py.

from A import foo
Run Code Online (Sandbox Code Playgroud)

现在python -m test_A.testpackage目录运行将在没有ImportError.

为什么这样有效?

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


Joe*_*how 11

from package.A import foo

我认为它比清楚的更清楚

import sys
sys.path.append("..")
Run Code Online (Sandbox Code Playgroud)

  • 它更可读,但仍然需要`sys.path.append("..")`.在python 3.6上测试过 (3认同)

pel*_*los 6

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

import alias
Run Code Online (Sandbox Code Playgroud)


dla*_*lin 6

正如最流行的答案所暗示的,基本上是因为您的PYTHONPATHsys.path包括.但不包括您通往包裹的路径。相对导入是相对于当前工作目录的,而不是相对于导入发生的文件的位置。奇怪。

您可以通过以下方法解决此问题:首先将相对导入更改为绝对导入,然后以以下内容开头:

PYTHONPATH=/path/to/package python -m test_A.test
Run Code Online (Sandbox Code Playgroud)

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

随着python -m test_A.test你在执行test_A/test.py__name__ == '__main__'__file__ == '/absolute/path/to/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())
Run Code Online (Sandbox Code Playgroud)


小智 6

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

from A import foo
Run Code Online (Sandbox Code Playgroud)


Mie*_*rpo 5

如果在提供了不错的答案后仍然有人在挣扎,请考虑检查以下内容:

https://www.daveoncode.com/2017/03/07/how-to-solve-python-modulenotfound-no-module-named-import-error/

以上网站的基本报价:

“可以通过这种方式以编程方式指定相同的内容:

导入系统

sys.path.append('..')

当然,以上代码必须在其他import 语句之前编写

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


Jer*_*ome 5

就我而言,我必须更改为:解决方案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
Run Code Online (Sandbox Code Playgroud)

解决方案2

import sys
import os
sys.path.append(os.getcwd())
from utils import MultiFileAllowed
Run Code Online (Sandbox Code Playgroud)