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

时间:2015-06-05 14:46:31

标签: python import package

这里似乎已经有很多关于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_A.test,根据我的理解,..A只上升一级,这仍然是在package文件夹中,为什么它会给出beyond top-level package的消息。导致此错误消息的原因是什么?

14 个答案:

答案 0 :(得分:130)

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

为什么它不起作用?这是因为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$PYTHONPATH处找到它。

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

为什么python不认为当前的工作目录是一个包? NO CLUE ,但是gosh它会很有用。

答案 1 :(得分:86)

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

试试这个。 为我工作。

答案 2 :(得分:34)

假设:
如果您位于package目录中,则Atest_A是单独的包。

结论:
..A导入仅允许在包中。

进一步说明:
如果要强制将包放在sys.path上的任何路径上,那么仅在包中提供相对导入是有用的。

编辑:

  

我是唯一一个认为这是疯了的人!?为什么世界上当前的工作目录不被认为是一个包? - Multihunter

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

答案 3 :(得分:9)

from package.A import foo

我认为它比

更清晰
import sys
sys.path.append("..")

答案 4 :(得分:7)

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

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

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

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

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


编辑:以下内容为我澄清了这一点:

import os
print (os.getcwd())

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

答案 5 :(得分:6)

这在Python中非常棘手

我将首先说明您为什么会遇到此问题,然后我将提及两种可能的解决方案。

  • 发生了什么事?

您必须考虑Python documentation中的这一段:

请注意,相对导入基于当前名称 模块。由于主模块的名称始终为“ 主要”, 旨在用作Python应用程序主模块的模块 必须始终使用绝对导入。

还有PEP 328中的以下内容:

相对导入使用模块的 name 属性来确定 模块在包层次结构中的位置。如果模块名称正确 不包含任何软件包信息(例如,它设置为“ 主要”) 然后解析相对导入,就好像该模块是顶级 模块,无论模块实际位于文件中的何处 系统。

相对导入从文件名(bool Person::lessThan(Person* per){ if(strcmp(this->name, per->name) < 0){ return true; }else{ return false; } } 属性)开始,该文件名可以采用两个值:

  1. 这是文件名,后跟strucutre文件夹,以点分隔。 例如:__name__ Python在这里知道父目录:package.test_A.testtest之前,然后是test_A因此您可以将点符号用于相对导入。
package

然后,您可以在根目录中拥有一个名为# package.test_A/test.py from ..A import foo 的根文件:

test.py
  1. 直接运行模块(# root.py from package.test_A import test )时,它成为程序的入口点,因此test.py == __name__。文件名没有指示目录结构,因此Python不知道如何进入目录。对于Python,__main__成为顶级脚本,上面没有任何内容。这就是为什么您不能使用相对导入的原因。

  • 可能的解决方案

A)一种解决方法是在根目录中有一个调用模块/软件包的根文件,如下所示:

enter image description here

  • test.py导入root.py。 (进入点,test.py)。
  • __name__ == __main__(相对)导入test.py
  • foo.py表示模块已导入。

输出为:

foo.py

B)如果您想将代码作为模块而不是顶级脚本执行,可以从命令行尝试:

package.A.foo has been imported
Module's name is:  package.test_A.test

欢迎任何建议。

您还应该检查Relative imports for the billionth time,特别是BrenBarn的答案。

答案 6 :(得分:4)

如果在提供了很好的答案后仍然有人在挣扎,请考虑检查以下内容:

https://www.daveoncode.com/2017/03/07/how-to-solve-python-modulenotfound-no-module-named-import-error/

上述网站的基本报价:

  

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

     

导入系统

     

sys.path.append('..')

     

当然,上面的代码必须在其他导入之前编写   声明。

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

答案 7 :(得分:4)

最流行的答案表明,基本上是因为您的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 :(得分:2)

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

import alias

答案 9 :(得分:1)

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

[情况1]当您开始像这样的绝对导入时

python -m test_A.test

import test_A.test

from test_A import test

您实际上是将 import-anchor 设置为test_A,换句话说,顶级包是test_A。因此,当我们执行test.py做from ..A import xxx时,您就逃脱了锚点,Python不允许这样做。

[情况2]当您这样做

python -m package.test_A.test

from package.test_A import test

您的锚点变为package,因此package/test_A/test.pyfrom ..A import xxx时不会逃脱锚点(仍然位于package文件夹中),Python会欣然接受。

简而言之:

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

此外,我们可以使用full-qualified module name(FQMN)检查此问题。

在每种情况下检查FQMN:

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

因此,对于CASE2,from .. import xxx将导致新模块的FQMN = {package.xxx,这是可以接受的。

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

答案 10 :(得分:1)

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

-t,--top-level-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且没有大戏剧的情况下。

答案 11 :(得分:1)

拥有

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

A/__init__.py 中导入 foo


from .foo import foo

A/ 导入 test_A/


import sys, os
sys.path.append(os.path.abspath('../A'))
# then import foo
import foo

答案 12 :(得分:0)

当我使用Django 2.1.3时,该命令对我不起作用:

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

我选择了一个自定义解决方案,在该解决方案中,我向服务器启动脚本添加了一个命令,以将共享脚本复制到需要共享python脚本的django“ app”中。 这不是理想的选择,但是由于我只是在开发一个个人网站,因此适合我。如果可以在单个网站内的Django Apps之间找到共享代码的django方式,我将再次在此处发布。

答案 13 :(得分:0)

就我而言,我不得不更改为: 解决方案1(更好,取决于当前的py文件路径。易于部署) 使用pathlib.Path.parents make code cleaner

import sys
import os
import pathlib
target_path = pathlib.Path(os.path.abspath(__file__)).parents[3]
sys.path.append(target_path)
from utils import MultiFileAllowed

解决方案2

import sys
import os
sys.path.append(os.getcwd())
from utils import MultiFileAllowed