问题:相对导入超出顶级包错误

似乎这里已经有很多关于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。究竟是什么原因导致此错误消息?

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找出与$PATHand 一起在哪里$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

正如最流行的答案所暗示的,基本上是因为您的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())

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.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。