相对导入时超出顶级包错误

2024-12-23 08:39:00
admin
原创
52
摘要:问题描述:似乎这里已经有很多关于 python 3 中的相对导入的问题,但是经过许多问题之后,我仍然没有找到我的问题的答案。所以这是问题。我有一个如下所示的包package/ __init__.py A/ __init__.py foo.py test_A/ ...

问题描述:

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


解决方案 1:

编辑:7 年后我重新审视了这个问题,并有了更好的理解。我把原来的答案留在这里,但我在这里写了一个更通用/更全面的答案。特别是,我现在知道了我在原始答案中提出的最后一个问题的答案。

其他更连贯的答案:

  • 兄弟包导入

  • 相对进口量达十亿次


为什么不起作用?这是因为 python 不记录包是从哪里加载的。所以当你这样做时,它基本上只是丢弃实际存储在 中的python -m test_A.test知识(即不被视为包)。尝试是试图访问它不再拥有的信息(即加载位置的兄弟目录)。它在概念上类似于允许中的文件。这会很糟糕,因为您希望包是不同的。如果他们需要使用另一个包中的东西,那么他们应该用 全局引用它们,并让 python 用和找出它在哪里。test_A.test`packagepackagefrom ..A import foofrom ..os import pathmathfrom os import path$PATH`$PYTHONPATH

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

为什么 python 不将当前工作目录视为包? 不知道,但天哪,这很有用。

解决方案 2:

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

试试这个。对我有用。

解决方案 3:

假设:

如果您在package目录中,A并且test_A是单独的包。

结论:

..A仅允许在包内导入。

进一步说明:

如果您想强制将包放置在位于的任何路径上,则使相对导入仅在包内可用很有用sys.path

编辑:

只有我一个人觉得这太疯狂了吗?!为什么当前工作目录不被视为包? – Multihunter

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

解决方案 4:

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

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

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

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

请注意单点,而不是迄今为止提到的双点解决方案。


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

import os
print (os.getcwd())

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

解决方案 5:

这在 Python 中非常棘手

我将首先评论您遇到该问题的原因,然后我会提到两种可能的解决方案。

这是怎么回事?

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

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

另外还有来自PEP 328 的以下内容:

相对导入使用模块的名称属性来确定模块在软件包层次结构中的位置。如果模块的名称不包含任何软件包信息(例如,设置为“ main ”),则相对导入将被视为模块是顶级模块,而不管模块在文件系统上的实际位置。

相对导入从文件名(__name__属性)开始,它可以采用两个值:

  1. 它是文件名,前面是文件夹结构,用点分隔。例如:package.test_A.test
    这里 Python 知道父目录:beforetesttest_Athen package

因此,您可以使用点符号进行相对导入。

#  package.test_A/test.py
from ..A import foo

然后,您可以在根目录中拥有一个类似根文件的文件,它调用test.py

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


可能的解决方案

  1. 解决这个问题的一种方法是有一个根文件(在根目录中)来调用模块/包,如下所示:

在此处输入图片描述

  • root.py导入test.py。(入口点,__name__ == __main__)。

  • test.py(相对)进口foo.py

  • foo.py表示模块已被导入。

输出为:

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

B)如果您想将模块作为顶级(独立)脚本执行,您可以从命令行尝试执行以下操作:

python -m package.test_A.test

您还应该检查:第十亿次的相对进口,特别是BrenBarn的答案。

解决方案 6:

from package.A import foo

我认为这比

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

解决方案 7:

正如最受欢迎的答案所暗示的那样,基本上是因为您的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:

这实际上比其他答案说的要简单得多。

TL;DRA直接导入,而不是尝试相对导入。

当前工作目录不是包,除非您从其他文件夹导入该文件夹package。因此,如果您打算让其他应用程序导入包,则包的行为将正常工作。不起作用的是测试...

无需改变目录结构中的任何内容,只需改变如何test.py导入foo.py

from A import foo

现在python -m test_A.testpackage目录运行将无需ImportError

为什么这样做有效?

您当前的工作目录不是包,但它添加到路径中。因此,您可以A直接导入文件夹及其内容。这与您可以导入已安装的任何其他包的原因相同……它们都包含在您的路径中。

解决方案 9:

编辑:2020-05-08:似乎我引用的网站不再由撰写建议的人控制,所以我删除了该网站的链接。感谢 baxx 告知我。


如果有人在阅读完前面提供的出色答案后仍然感到有些困惑,我在一个不再可用的网站上找到了建议。

我提到的网站上的重要引言:

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

导入系统

sys.path.append('..')

当然,上面的代码必须写在其他导入
语句之前。

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

解决方案 10:

..只需在 test.py 中
删除即可。对我来说,pytest 可以很好地运行,
例如:

from A import foo

解决方案 11:

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

import alias

解决方案 12:

就我而言,我必须改成这样:解决方案 1(更好的是,它依赖于当前的 py 文件路径。易于部署)使用pathlib.Path.parents 使代码更简洁

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

解决方案 13:

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

[案例 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.pyfrom ..A import xxx并不会逃离锚点(仍然在package文件夹内),Python 很乐意接受这一点。

简而言之:

  • 绝对导入改变当前锚点(=重新定义什么是顶级包);

  • 相对导入不会改变锚点但会将其限制在其中。

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

检查每种情况下的 FQMN:

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

  • [案例1] test.__name__=test_A.test

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

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

[2022-07-19] 我认为这个“相对导入”限制是一个相当丑陋的设计,完全违背了 Python 的座右铭“简单胜于复杂”之一。

解决方案 14:

拥有

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

解决方案 15:

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

-t, --top-level-directory 目录 项目的顶级目录(默认为起始目录)

因此,在像这样的结构上

project_root
  |
  |----- my_module
  |          \n  |           _____ 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无重大戏剧。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   990  
  在项目管理领域,CDCP(Certified Data Center Professional)认证评审是一个至关重要的环节,它不仅验证了项目团队的专业能力,还直接关系到项目的成功与否。在这一评审过程中,沟通技巧的运用至关重要。有效的沟通不仅能够确保信息的准确传递,还能增强团队协作,提升评审效率。本文将深入探讨CDCP...
华为IPD流程   26  
  IPD(Integrated Product Development,集成产品开发)是一种以客户需求为核心、跨部门协同的产品开发模式,旨在通过高效的资源整合和流程优化,提升产品开发的成功率和市场竞争力。在IPD培训课程中,掌握关键成功因素是确保团队能够有效实施这一模式的核心。以下将从五个关键成功因素展开讨论,帮助企业和...
IPD项目流程图   27  
  华为IPD(Integrated Product Development,集成产品开发)流程是华为公司在其全球化进程中逐步构建和完善的一套高效产品开发管理体系。这一流程不仅帮助华为在技术创新和产品交付上实现了质的飞跃,还为其在全球市场中赢得了显著的竞争优势。IPD的核心在于通过跨部门协作、阶段性评审和市场需求驱动,确保...
华为IPD   26  
  华为作为全球领先的通信技术解决方案提供商,其成功的背后离不开一套成熟的管理体系——集成产品开发(IPD)。IPD不仅是一种产品开发流程,更是一种系统化的管理思想,它通过跨职能团队的协作、阶段评审机制和市场需求驱动的开发模式,帮助华为在全球市场中脱颖而出。从最初的国内市场到如今的全球化布局,华为的IPD体系在多个领域展现...
IPD管理流程   53  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用