关于python:超出相对导入中的顶级包错误

beyond top level package error in relative import

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

我有一个如下所示的包裹

1
2
3
4
5
6
7
8
package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

我在test.py中有一行:

1
from ..A import foo

现在,我在package的文件夹中,运行

1
python -m test_A.test

我得到了信息

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

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

1
2
cd ..
python -m package.test_A.test

一切都很好。

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


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

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

为什么不起作用?这是因为python不记录包的加载位置。所以当你做python -m test_A.test的时候,基本上只是放弃了test_A.test实际上是存储在package中的知识(即package不被认为是一个包)。尝试from ..A import foo是试图访问它不再拥有的信息(即加载位置的同级目录)。这在概念上类似于允许from ..os import pathmath的文件中。这将很糟糕,因为您希望包是不同的。如果他们需要从另一个包中使用某些内容,那么他们应该使用from os import path在全球范围内引用这些内容,并让python计算出这些内容在$PATH$PYTHONPATH中的位置。

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

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


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

试试这个。为我工作。


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

结论:..A进口仅允许在包装内进行。

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

编辑:

Am I the only one who thinks that this is insane!? Why in the world is the current working directory not considered to be a package? – Multihunter

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


from package.A import foo

我觉得它比

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


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

1
2
3
4
5
6
package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

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

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

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

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

1
2
import os
print (os.getcwd())

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


如果在已经提供了好的答案之后,仍有人在挣扎,请考虑查看以下内容:

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

以上网站的基本报价:

"The same can be specified programmatically in this way:

import sys

sys.path.append('..')

Of course the code above must be written before the other import
statement.

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


正如最流行的答案所表明的,基本上是因为你的PYTHONPATHsys.path包括.,而不是你的包裹路径。相对导入是相对于当前工作目录的,而不是导入发生的文件;奇怪的是。

您可以通过先将相对导入更改为绝对导入,然后从以下任一项开始来解决此问题:

1
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路径操作:

1
2
3
4
5
6
7
8
9
10
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 file/path as alias。然后您可以在较低的脚本上使用它,如下所示:

1
import alias