问题:相对导入超出顶级包错误
似乎这里已经有很多关于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
根据我的理解,我在test_A子软件包中运行模块,原因是,该模块..A
仅上升了一层(仍位于该package
文件夹中),为什么它给出消息说beyond top-level package
。究竟是什么原因导致此错误消息?
回答 0
编辑:在其他问题中,这个问题有更好/更连贯的答案:
为什么不起作用?这是因为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
and 一起在哪里$PYTHONPATH
。
使用时python -m package.test_A.test
,使用from ..A import foo
解析就可以了,因为它可以跟踪其中的内容,package
而您只是访问已加载位置的子目录。
python为什么不将当前工作目录视为软件包? 没有线索,但是天哪,它将很有用。
回答 1
import sys
sys.path.append("..") # Adds higher directory to python modules path.
尝试这个。为我工作。
回答 2
假设:
如果您在package
目录中,A
并且test_A
是单独的软件包。
结论:..A
只允许在包装内进口。
进一步说明:
如果要强制将软件包放置在上的任何路径上,则使相对导入仅在软件包内可用是很有用的sys.path
。
编辑:
我是唯一认为这很疯狂的人吗?为什么在世界上当前的工作目录不被视为软件包?–多猎人
当前的工作目录通常位于sys.path中。因此,所有文件都可以导入。这是自Python 2以来尚不存在的软件包的行为。将运行目录打包,可以将模块导入为“ import .A”和“ import A”,这将是两个不同的模块。也许这是一个不一致的考虑。
回答 3
在3.6中,这些解决方案都不适用于我,其文件夹结构如下:
package1/
subpackage1/
module1.py
package2/
subpackage2/
module2.py
我的目标是从module1导入module2。最终对我有用的是:
import sys
sys.path.append(".")
请注意,单点与到目前为止提到的两点解决方案不同。
编辑:以下帮助为我澄清了这一点:
import os
print (os.getcwd())
就我而言,工作目录是(意外地)项目的根目录。
回答 4
from package.A import foo
我认为这比
import sys
sys.path.append("..")
回答 5
正如最流行的答案所暗示的,基本上是因为您的PYTHONPATH
或sys.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())
回答 6
编辑:2020-05-08:看来我引用的网站不再受撰写建议的人控制,因此我正在删除指向该网站的链接。感谢您让我知道baxx。
如果在提供了很好的答案后仍然有人在挣扎,我在网站上找不到了建议。
我提到的网站的基本报价:
“可以通过这种方式以编程方式指定相同的内容:
导入系统
sys.path.append(’..’)
当然,以上代码必须在其他import 语句之前编写。
很明显,必须这样,事后才思考。我试图在测试中使用sys.path.append(’..’),但遇到了OP发布的问题。通过在其他导入之前添加import和sys.path定义,我可以解决此问题。
回答 7
如果__init__.py
在上层文件夹中有一个,则可以像import file/path as alias
在该init文件中一样初始化导入
。然后,您可以在较低的脚本上使用它,如下所示:
import alias
回答 8
以我的拙见,我以这种方式理解这个问题:
[案例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不允许这样做。
回答 9
不确定在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
没有重大戏剧的地方。