问题:相对导入超出顶级包错误
似乎这里已经有很多关于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
。究竟是什么原因导致此错误消息?
It seems there are already quite some questions here about relative import in python 3, but after going through many of them I still didn’t find the answer for my issue.
so here is the question.
I have a package shown below
package/
__init__.py
A/
__init__.py
foo.py
test_A/
__init__.py
test.py
and I have a single line in test.py:
from ..A import foo
now, I am in the folder of package
, and I run
python -m test_A.test
I got message
"ValueError: attempted relative import beyond top-level package"
but if I am in the parent folder of package
, e.g., I run:
cd ..
python -m package.test_A.test
everything is fine.
Now my question is:
when I am in the folder of package
, and I run the module inside the test_A sub-package as test_A.test
, based on my understanding, ..A
goes up only one level, which is still within the package
folder, why it gives message saying beyond top-level package
. What is exactly the reason that causes this error message?
回答 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为什么不将当前工作目录视为软件包? 没有线索,但是天哪,它将很有用。
EDIT: There are better/more coherent answers to this question in other questions:
Why doesn’t it work? It’s because python doesn’t record where a package was loaded from. So when you do python -m test_A.test
, it basically just discards the knowledge that test_A.test
is actually stored in package
(i.e. package
is not considered a package). Attempting from ..A import foo
is trying to access information it doesn’t have any more (i.e. sibling directories of a loaded location). It’s conceptually similar to allowing from ..os import path
in a file in math
. This would be bad because you want the packages to be distinct. If they need to use something from another package, then they should refer to them globally with from os import path
and let python work out where that is with $PATH
and $PYTHONPATH
.
When you use python -m package.test_A.test
, then using from ..A import foo
resolves just fine because it kept track of what’s in package
and you’re just accessing a child directory of a loaded location.
Why doesn’t python consider the current working directory to be a package? NO CLUE, but gosh it would be useful.
回答 1
import sys
sys.path.append("..") # Adds higher directory to python modules path.
尝试这个。为我工作。
import sys
sys.path.append("..") # Adds higher directory to python modules path.
Try this.
Worked for me.
回答 2
假设:
如果您在package
目录中,A
并且test_A
是单独的软件包。
结论:
..A
只允许在包装内进口。
进一步说明:
如果要强制将软件包放置在上的任何路径上,则使相对导入仅在软件包内可用是很有用的sys.path
。
编辑:
我是唯一认为这很疯狂的人吗?为什么在世界上当前的工作目录不被视为软件包?–多猎人
当前的工作目录通常位于sys.path中。因此,所有文件都可以导入。这是自Python 2以来尚不存在的软件包的行为。将运行目录打包,可以将模块导入为“ import .A”和“ import A”,这将是两个不同的模块。也许这是一个不一致的考虑。
Assumption:
If you are in the package
directory, A
and test_A
are separate packages.
Conclusion:
..A
imports are only allowed within a package.
Further notes:
Making the relative imports only available within packages is useful if you want to force that packages can be placed on any path located on sys.path
.
EDIT:
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
The current working directory is usually located in sys.path. So, all files there are importable. This is behavior since Python 2 when packages did not yet exist. Making the running directory a package would allow imports of modules as “import .A” and as “import A” which then would be two different modules. Maybe this is an inconsistency to consider.
回答 3
在3.6中,这些解决方案都不适用于我,其文件夹结构如下:
package1/
subpackage1/
module1.py
package2/
subpackage2/
module2.py
我的目标是从module1导入module2。最终对我有用的是:
import sys
sys.path.append(".")
请注意,单点与到目前为止提到的两点解决方案不同。
编辑:以下帮助为我澄清了这一点:
import os
print (os.getcwd())
就我而言,工作目录是(意外地)项目的根目录。
None of these solutions worked for me in 3.6, with a folder structure like:
package1/
subpackage1/
module1.py
package2/
subpackage2/
module2.py
My goal was to import from module1 into module2. What finally worked for me was, oddly enough:
import sys
sys.path.append(".")
Note the single dot as opposed to the two-dot solutions mentioned so far.
Edit: The following helped clarify this for me:
import os
print (os.getcwd())
In my case, the working directory was (unexpectedly) the root of the project.
回答 4
from package.A import foo
我认为这比
import sys
sys.path.append("..")
from package.A import foo
I think it’s clearer than
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())
As the most popular answer suggests, basically its because your PYTHONPATH
or sys.path
includes .
but not your path to your package. And the relative import is relative to your current working directory, not the file where the import happens; oddly.
You could fix this by first changing your relative import to absolute and then either starting it with:
PYTHONPATH=/path/to/package python -m test_A.test
OR forcing the python path when called this way, because:
With python -m test_A.test
you’re executing test_A/test.py
with __name__ == '__main__'
and __file__ == '/absolute/path/to/test_A/test.py'
That means that in test.py
you could use your absolute import
semi-protected in the main case condition and also do some one-time Python path manipulation:
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定义,我可以解决此问题。
Edit: 2020-05-08: Is seems the website I quoted is no longer controlled by the person who wrote the advice, so I’m removing the link to the site. Thanks for letting me know baxx.
If someone’s still struggling a bit after the great answers already provided, I found advice on a website that no longer is available.
Essential quote from the site I mentioned:
“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.
It’s pretty obvious that it has to be this way, thinking on it after the fact. I was trying to use the sys.path.append(‘..’) in my tests, but ran into the issue posted by OP. By adding the import and sys.path defintion before my other imports, I was able to solve the problem.
回答 7
如果__init__.py
在上层文件夹中有一个,则可以像import file/path as alias
在该init文件中一样初始化导入
。然后,您可以在较低的脚本上使用它,如下所示:
import alias
if you have an __init__.py
in an upper folder, you can initialize the import as
import file/path as alias
in that init file. Then you can use it on lower scripts as:
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不允许这样做。
In my humble opinion, I understand this question in this way:
[CASE 1] When you start an absolute-import like
python -m test_A.test
or
import test_A.test
or
from test_A import test
you’re actually setting the import-anchor to be test_A
, in other word, top-level package is test_A
. So, when we have test.py do from ..A import xxx
, you are escaping from the anchor, and Python does not allow this.
[CASE 2] When you do
python -m package.test_A.test
or
from package.test_A import test
your anchor becomes package
, so package/test_A/test.py
doing from ..A import xxx
does not escape the anchor(still inside package
folder), and Python happily accepts this.
In short:
- Absolute-import changes current anchor (=redefines what is the top-level package);
- Relative-import does not change the anchor but confines to it.
Furthermore, we can use full-qualified module name(FQMN) to inspect this problem.
Check FQMN in each case:
- [CASE2]
test.__name__
= package.test_A.test
- [CASE1]
test.__name__
= test_A.test
So, for CASE2, an from .. import xxx
will result in a new module with FQMN=package.xxx
, which is acceptable.
While for CASE1, the ..
from within from .. import xxx
will jump out of the starting node(anchor) of test_A
, and this is NOT allowed by 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
没有重大戏剧的地方。
Not sure in python 2.x but in python 3.6, assuming you are trying to run the whole suite, you just have to use -t
-t, –top-level-directory directory
Top level directory of project (defaults to start directory)
So, on a structure like
project_root
|
|----- my_module
| \
| \_____ my_class.py
|
\ tests
\___ test_my_func.py
One could for example use:
python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/
And still import the my_module.my_class
without major dramas.