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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30669474/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 08:48:09  来源:igfitidea点击:

beyond top level package error in relative import

pythonimportpackage

提问by shelper

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.

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

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:

我在 test.py 中有一行:

from ..A import foo

now, I am in the folder of package, and I run

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

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:

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

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, ..Agoes up only one level, which is still within the packagefolder, why it gives message saying beyond top-level package. What is exactly the reason that causes this error message?

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

回答by User

Assumption:
If you are in the packagedirectory, Aand test_Aare separate packages.

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

Conclusion:
..Aimports are only allowed within a package.

结论:
..A只允许在包内导入。

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.

进一步说明:
如果您想强制将包放置在位于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.

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

回答by jenish Sakhiya

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

Try this. Worked for me.

尝试这个。为我工作。

回答by Multihunter

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.testis actually stored in package(i.e. packageis not considered a package). Attempting from ..A import foois 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 pathin 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 pathand let python work out where that is with $PATHand $PYTHONPATH.

为什么不起作用?这是因为 python 没有记录包是从哪里加载的。所以当你这样做时python -m test_A.test,它基本上只是丢弃test_A.test实际存储的知识package(即package不被视为包)。正在from ..A import foo尝试访问它不再拥有的信息(即加载位置的同级目录)。它在概念上类似于允许from ..os import pathmath. 这会很糟糕,因为您希望包是不同的。如果他们需要使用一些从另一个包,那么他们应该是指他们与全球from os import path,让蟒蛇在哪里工作是与$PATH$PYTHONPATH

When you use python -m package.test_A.test, then using from ..A import fooresolves just fine because it kept track of what's in packageand you're just accessing a child directory of a loaded location.

当您使用python -m package.test_A.test,然后 usingfrom ..A import foo解析就好了,因为它会跟踪其中的内容,package而您只是在访问已加载位置的子目录。

Why doesn't python consider the current working directory to be a package?NO CLUE, but gosh it would be useful.

为什么python不认为当前工作目录是一个包?不知道,但天哪,它会很有用。

回答by Joe Zhow

from package.A import foo

from package.A import foo

I think it's clearer than

我认为它比

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

回答by Mierpo

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.

编辑:2020-05-08:我引用的网站似乎不再由撰写建议的人控制,所以我删除了该网站的链接。谢谢你让我知道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 importstatement.

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

导入系统

sys.path.append('..')

当然上面的代码必须写在另一个import语句之前

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.

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

回答by pelos

if you have an __init__.pyin an upper folder, you can initialize the import as import file/path as aliasin that init file. Then you can use it on lower scripts as:

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

import alias

回答by Jason DeMorrow

None of these solutions worked for me in 3.6, with a folder structure like:

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

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:

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

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.

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

回答by dlamblin

As the most popular answer suggests, basically its because your PYTHONPATHor sys.pathincludes .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.

正如最受欢迎的答案所暗示的那样,基本上是因为您的PYTHONPATHsys.path包含.但不是您的包裹路径。而相对导入是相对于你当前的工作目录,而不是导入发生的文件;奇怪。

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:

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

With python -m test_A.testyou're executing test_A/test.pywith __name__ == '__main__'and __file__ == '/absolute/path/to/test_A/test.py'

随着python -m test_A.test你在执行test_A/test.py__name__ == '__main__'__file__ == '/absolute/path/to/test_A/test.py'

That means that in test.pyyou could use your absolute importsemi-protected in the main case condition and also do some one-time Python path manipulation:

这意味着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())

回答by Jimm Chen

In my humble opinion, I understand this question in this way:

以我的拙见,我是这样理解这个问题的:

[CASE 1] When you start an absolute-import like

[案例 1] 当您开始绝对导入时

python -m test_A.test

or

或者

import test_A.test

or

或者

from test_A import test

you're actually setting the import-anchorto 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.

您实际上将导入锚点设置为test_A,换句话说,顶级包为test_A. 所以,当我们有 test.py do 时from ..A import xxx,你正在逃离锚点,而 Python 不允许这样做。

[CASE 2] When you do

[案例 2] 当你这样做时

python -m package.test_A.test

or

或者

from package.test_A import test

your anchor becomes package, so package/test_A/test.pydoing from ..A import xxxdoes not escape the anchor(still inside packagefolder), and Python happily accepts this.

你的锚点变成package,所以package/test_A/test.py这样做from ..A import xxx并没有逃脱锚点(仍在package文件夹中),Python 很高兴地接受了这一点。

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.

此外,我们可以使用全限定模块名称(FQMN)来检查这个问题。

Check FQMN in each case:

在每种情况下检查 FQMN:

  • [CASE2] test.__name__= package.test_A.test
  • [CASE1] test.__name__= test_A.test
  • [案例2] test.__name__=package.test_A.test
  • [案例1] test.__name__=test_A.test

So, for CASE2, an from .. import xxxwill result in a new module with FQMN=package.xxx, which is acceptable.

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

While for CASE1, the ..from within from .. import xxxwill jump out of the starting node(anchor) of test_A, and this is NOT allowed by Python.

而对于 CASE1,..from insidefrom .. import xxx会跳出 的起始节点(锚点)test_A,这是 Python 不允许的。

回答by Andre de Miranda

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

在 python 2.x 中不确定,但在 python 3.6 中,假设您正在尝试运行整个套件,您只需要使用 -t

-t, --top-level-directory directory Top level directory of project (defaults to start directory)

-t, --top-level-directory 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/

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

And still import the my_module.my_classwithout major dramas.

并且仍然导入my_module.my_class没有主要的戏剧。