相对导入超出顶级包错误


316

似乎这里已经有很多关于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

一切安好。

现在我的问题是: 当我位于的文件夹中时packagetest_A.test根据我的理解,我在test_A子软件包中运行模块,原因是,该模块..A仅上升了一层(仍位于该package文件夹中),为什么它给出消息说beyond top-level package。究竟是什么原因导致此错误消息?


49
该帖子没有解释我的“超出顶级套餐”的错误
Shelper 2015年

4
我在这里有一个想法,所以当运行test_A.test作为模块时,“ ..”高于test_A(已经是导入test_A.test的最高级别),我认为程序包级别不是目录级别,而是多少?导入软件包的级别。
shelper 2015年

2
我保证您在看完这个答案stackoverflow.com/a/14132912/8682868后,您将了解有关相对导入的所有信息。
pzjzeason

有关此问题的详细说明,请参见ValueError:尝试相对顶级包进行相对导入
napuzba

有没有办法避免做相对进口?例如Eclipse中的PyDev如何查看<PydevProject> / src中的所有软件包?
Mushu909 '19

Answers:


172

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


为什么不起作用?这是因为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找出与$PATHand 一起在哪里$PYTHONPATH

使用时python -m package.test_A.test,使用from ..A import foo解析就可以了,因为它可以跟踪其中的内容,package而您只是访问已加载位置的子目录。

python为什么不将当前工作目录视为软件包? 没有线索,但是天哪,它将很有用。


2
我已经对答案进行了编辑,以指的是对同一个问题的更好回答。只有解决方法。我真正看到的唯一工作就是OP所做的事情,即使用该-m标志并从上面的目录中运行。
Multihunter

1
应该注意的是,从Multihunter给出的链接来看,这个答案并不涉及sys.path黑客,而是使用setuptools,在我看来这更有趣。
Angelo Cardellicchio

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

尝试这个。为我工作。


10
嗯...如何工作?每个测试文件都会有这个吗?
乔治·莫尔

这里的问题是,例如,如果A/bar.py存在,而foo.py您确实存在from .bar import X
user1834164

9
添加sys.path.append(“ ..”)后,我不得不从“ ..a import ...”中删除..
Jake OPJ

2
如果从存在目录的外部执行脚本,则该脚本将无效。相反,您必须调整此答案以指定所述脚本的绝对路径
Manavalan Gajapathy

这是最好,最简单的选择
Alex R

43

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

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

进一步说明:
如果要强制将软件包放置在上的任何路径上,则使相对导入仅在软件包内可用是很有用的sys.path

编辑:

我是唯一认为这很疯狂的人吗?为什么在世界上当前的工作目录不被视为软件包?–多猎人

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


85
我是唯一认为这很疯狂的人吗?为什么在世界范围内,运行目录不被视为软件包?
Multihunter

13
不仅如此,这无济于事...那么您该如何运行测试?显然,OP在询问的事情以及为什么我也肯定有很多人在这里。
乔治·莫尔

运行目录通常位于sys.path中。因此,所有文件都可以导入。这是自Python 2以来尚不存在的软件包的行为。-编辑答案。
用户

我不遵循这种矛盾。的行为python -m package.test_A.test似乎可以满足您的要求,我的观点是,这应该是默认设置。那么,您能举个例子说明这种矛盾吗?
Multihunter

我实际上在思考,对此是否有功能要求?确实是疯了。C / C ++样式#include将非常有用!
Nicholas Humphrey

29

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

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

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

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

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


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

import os
print (os.getcwd())

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


2
它在本地工作,但不适用于AWS ec2实例,这有意义吗?
thebeancounter

这也对我有用-在我的情况下,工作目录同样是项目根目录。我使用的是编程编辑器(TextMate)的运行快捷方式
JeremyDouglass

@thebeancounter一样!在Mac上本地工作,但在ec2上不工作,然后我意识到我正在ec2的子目录中运行命令,并在本地root运行。一旦我在ec2上从根目录运行它,它就会起作用。
Logan Yang

这也为我工作非常感谢。通过该sys方法,我现在可以简单地调用该软件包,而无需“ ..”
RamWill

sys.path.append(".")工作,因为你是在父目录调用它,注意.始终代表所在的目录中运行python命令。
KevinZhou

13

from package.A import foo

我认为这比

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

4
当然,它更具可读性,但仍然需要sys.path.append("..")。在python 3.6上测试
MFA

与较早的答案相同
nrofis

12

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

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

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

或以这种方式强制执行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())

8

编辑:2020-05-08:看来我引用的网站不再受撰写建议的人控制,因此我正在删除指向该网站的链接。感谢您让我知道baxx。


如果在提供了很好的答案后仍然有人在挣扎,我在网站上找不到了建议。

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

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

导入系统

sys.path.append('..')

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

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


您发布的链接已消失。
baxx

谢谢你让我知道。域名似乎不再由同一个人控制。我已删除链接。
Mierpo

5

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

import alias

0

以我的拙见,我以这种方式理解这个问题:

[案例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.py这样from ..A import xxx做不会逃脱锚(仍位于package文件夹中),Python会很乐意接受这一点。

简而言之:

  • 绝对导入更改当前锚点(=重新定义顶级程序包);
  • 相对导入不会更改锚,但会限制在锚内。

此外,我们可以使用标准模块名称(FQMN)来检查此问题。

分别检查FQMN:

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

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

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


2
这比需要的要复杂得多。对于Zen的Python来说,这是如此多。
AtilioA

0

不确定在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没有重大戏剧的地方。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.