如何修复"非包装中的尝试相对导入",即使使用__init__.py也是如此

sky*_*der 713 python init package python-import importerror

我正在尝试遵循PEP 328,具有以下目录结构:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py
Run Code Online (Sandbox Code Playgroud)

core_test.py我有以下import语句

from ..components.core import GameLoopEvents
Run Code Online (Sandbox Code Playgroud)

但是,当我运行时,我收到以下错误:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package
Run Code Online (Sandbox Code Playgroud)

搜索周围我发现" 相对路径甚至不能使用__init__.py "和" 从相对路径导入模块 ",但它们没有帮助.

这里有什么我想念的吗?

Bre*_*arn 612

详细阐述Ignacio Vazquez-Abrams的答案:

Python导入机制相对于__name__当前文件的工作方式.直接执行文件时,它没有通常的名称,而是具有"__main__"其名称.所以相对进口不起作用.

正如Igancio建议的那样,您可以使用该-m选项执行它.如果您的程序包的一部分要作为脚本运行,您还可以使用该__package__属性来告诉该文件它应该在程序包层次结构中具有什么名称.

有关详细信息,请访问http://www.python.org/dev/peps/pep-0366/.

  • 花了一段时间让我意识到你不能从`tests`子目录中运行`python -m core_test` - 它必须来自父节点,或者你必须将父节点添加到路径中. (53认同)
  • @DannyStaple:嗯,它的工作原理在链接文档中有所描述.如果你在`pack.subpack`包中有一个脚本`script.py`,那么将它的`__package__`设置为`pack.subpack`将允许你从`.module导入一些东西`来从`pack.module导入一些东西`.请注意,正如文档所述,您仍然必须在系统路径上拥有顶级包.这已经成为导入模块的工作方式.`_package__`唯一能做的就是让你将这种行为用于直接执行的脚本. (4认同)
  • @DannyStaple:不完全是.您可以使用`__package__`来确保可执行脚本文件可以从相同的包中相对导入其他模块.没有办法从"整个系统"相对导入.我甚至不确定你为什么要这样做. (3认同)
  • 我在直接执行的脚本中使用了“ __package__”,但是不幸的是,出现以下错误:“未加载父模块'xxx',无法执行相对导入” (3认同)
  • 我的意思是如果`__package__`符号设置为"parent.child",那么你就可以导入"parent.other_child".也许我没有这么说. (2认同)

Ign*_*ams 426

是.你没有把它作为一个包使用.

python -m pkg.tests.core_test
Run Code Online (Sandbox Code Playgroud)

  • 考虑到这个问题和答案的流行,我不是其中的任何一个,但我觉得这可以使用*相当多的*更多细节.注意到从什么目录执行上面的shell命令,你需要`__init __.py一直向下,以及`__package__`修改技巧(下面由BrenBarn描述)需要允许这些导入可执行文件脚本(例如,当使用shebang并在Unix shell上执行`./ my_script.py`时)都会很有用.对于我来说,找出或找到简洁易懂的文档对整个问题来说非常棘手. (472认同)
  • 说真的,你能解释一下你的答案中会发生什么吗? (86认同)
  • 问题:请注意,最后没有'.py'! (49认同)
  • 注意:您需要在CLI中调用此行的位置`pkg`之外.然后,它应该按预期工作.如果你在`pkg`里面,你调用`python -m tests.core_test`,它将无法正常工作.至少它不适合我. (15认同)
  • @MarkAmery几乎失去了我的想法试图弄清楚这一切是如何工作的,项目中的相对导入与具有`__init __.py`文件的py文件的子目录但你仍然得到`ValueError:尝试非包中的相对导入'错误.我会为某个人付出非常好的钱,在某个地方,最后用简单的英语解释所有这些是如何工作的. (15认同)
  • 例如,如果您在组件dir中运行脚本,则这不起作用 (5认同)
  • 有一个[下面很好地阐述了最近的答案](http://stackoverflow.com/a/27876800/838992),其中包含了upvoted评论建议的文档链接. (3认同)
  • 与Python中的FAQ的答案相反,这个答案并不清楚发生了什么以及执行此命令的位置 (2认同)
  • 我不确定将其作为模块运行如何帮助 @skytreader 从另一个脚本中导入问题...... (2认同)
  • 也许我不太了解 SO 上类似 wiki 的功能的文化规范,但值得注意的是,没有人指出如何改进 Ignacio 的答案实际上就地改进了它。(请注意,我 100% 同意解释会非常有帮助的评论。) (2认同)

ihm*_*ihm 198

import components.core如果将当前目录附加到sys.path以下目的,则可以直接使用:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
Run Code Online (Sandbox Code Playgroud)

  • `sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__ file __),'..')))`这个也行 (34认同)
  • `from os import sys`看起来像作弊:) (23认同)
  • 仅供参考,为了在ipython笔记本中使用它,我将这个答案改编为:`import os; os.sys.path.append(os.path.dirname(os.path.abspath则( '')))`.然后直接`import components.core`适用于我,根据需要从笔记本的父目录导入. (10认同)
  • @flyingsheep:同意,我只是使用常规`import sys,os.path作为路径`. (7认同)
  • @Piotr:它可能被认为更好,因为它稍微清楚地表明了什么是附加到`sys.path` - 当前文件所在目录的父目录. (3认同)
  • @ajay和你的更好因为什么? (2认同)
  • @martineau我认为你是[正确](https://github.com/pfmoore/pip/commit/87418e05bb55ea64476653dd41e2f4db785f02c0#commitcomment-5418772):) (2认同)
  • @Piotr:啊,是的,我必须同意`sys.path.append(path.dirname(path.dirname(__ file __)))`会更清楚 - 并且评论完美. (2认同)

Pao*_*lli 178

这取决于您希望如何启动脚本.

如果要以经典方式从命令行启动UnitTest,即:

python tests/core_test.py
Run Code Online (Sandbox Code Playgroud)

然后,因为在这种情况下'components''tests'是兄弟文件夹,你可以使用sys.path模块的insertappend方法导入相关模块.就像是:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents
Run Code Online (Sandbox Code Playgroud)

否则,您可以使用'-m'参数启动脚本(请注意,在这种情况下,我们正在讨论一个包,因此您不能提供'.py'扩展名),即:

python -m pkg.tests.core_test
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您可以像以前一样使用相对导入:

from ..components.core import GameLoopEvents
Run Code Online (Sandbox Code Playgroud)

您最终可以混合使用这两种方法,这样无论调用方式如何,您的脚本都可以正常工作.例如:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents
Run Code Online (Sandbox Code Playgroud)

  • 如果我尝试使用pdb进行调试,该怎么办?因为你使用`python -m pdb myscript.py`来启动调试会话. (3认同)
  • 使用`insert`而不是`append`会更好吗?也就是说,`sys.path.insert(0,os.path.dirname(os.path.dirname(os.path.abspath(__ file __))))` (3认同)
  • 使用insert可以更好地匹配相对导入语义,其中本地包名称优先于已安装的包.特别是对于测试,您通常要测试本地版本,而不是已安装的版本(除非您的测试基础架构安装了测试中的代码,在这种情况下,不需要相关导入,您将不会遇到此问题). (2认同)
  • 您还应该提到,当您作为模块运行时,您不能位于包含 core_test 的目录中(这太简单了) (2认同)

All*_*gwa 20

在core_test.py中,执行以下操作:

import sys
sys.path.append('../components')
from core import GameLoopEvents
Run Code Online (Sandbox Code Playgroud)


Moh*_*med 10

问题在于您的测试方法,

你试过了 python core_test.py

那么你会得到这个错误 ValueError: Attempted relative import in non-package

原因:您正在从非包装源测试您的包装。

所以从包源测试你的模块。

如果这是您的项目结构,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py
Run Code Online (Sandbox Code Playgroud)

光盘包

python -m tests.core_test # dont use .py
Run Code Online (Sandbox Code Playgroud)

或来自外部 pkg/

python -m pkg.tests.core_test
Run Code Online (Sandbox Code Playgroud)

.如果要从同一目录中的文件夹导入,则单个。每后退一步,再加一个。

hi/
  hello.py
how.py
Run Code Online (Sandbox Code Playgroud)

how.py

from .hi import hello
Run Code Online (Sandbox Code Playgroud)

如果你想从 hello.py 导入 how

from .. import how
Run Code Online (Sandbox Code Playgroud)


dee*_*pak 9

如果您的用例是用于运行测试,并且它是接缝,那么您可以执行以下操作.而不是像python core_test.py使用测试框架那样运行测试脚本pytest.然后在命令行上输入

$$ py.test
Run Code Online (Sandbox Code Playgroud)

这将在您的目录中运行测试.这得到周围人的问题__name__的存在__main__,是由@BrenBarn指出.接下来,将一个空__init__.py文件放入您的测试目录中,这将使测试目录成为您的包的一部分.然后你就可以做到

from ..components.core import GameLoopEvents
Run Code Online (Sandbox Code Playgroud)

但是,如果您将测试脚本作为主程序运行,那么事情将再次失败.所以只需使用测试运行器.也许这也适用于其他测试跑步者,nosetests但我还没有检查过.希望这可以帮助.


v4g*_*gil 7

我的快速修复是将目录添加到路径:

import sys
sys.path.insert(0, '../components/')
Run Code Online (Sandbox Code Playgroud)

  • 您的方法在所有情况下都不起作用,因为'../'部分是从您运行脚本的目录(core_test.py)中解析的.使用您的方法,您必须在运行core_test.py scritp之前cd到'tests'. (5认同)

sur*_*nto 7

由于您已经将所有内容标记为模块,因此如果您将其作为 Python 模块启动,则无需使用相对引用。

代替

from ..components.core import GameLoopEvents
Run Code Online (Sandbox Code Playgroud)

简单地

from pkg.components.core import GameLoopEvents
Run Code Online (Sandbox Code Playgroud)

当您从包的父包运行时,请使用以下命令:

python -m pkg.tests.core_test
Run Code Online (Sandbox Code Playgroud)