即使使用__init__.py,也如何解决“尝试以非软件包方式进行相对导入”


744

我正在尝试使用以下目录结构来遵循PEP 328

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

core_test.py我有以下进口声明

from ..components.core import GameLoopEvents

但是,当我运行时,出现以下错误:

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

到处搜索时,我发现“ 即使使用__init__.py,相对路径也无法使用 ”和“ 从相对路径导入模块 ”,但是它们没有帮助。

我在这里想念什么吗?


17
我对构建unittest项目的各种方式也感到非常困惑,所以我写了这个相当详尽的示例项目,其中涵盖了模块的深层嵌套,相对和绝对导入(在哪里可以工作,不可以)以及从一个内部引用相对和绝对引用。包,以及类的单,双和包级导入。清除了事情直到我!
cod3monk3y 2014年

1
我无法让您的测试正常工作。no module named myimports.foo当我运行它们时,请不断获取。
Blairg23

@ Blairg23我猜想预期的调用是例如cd进入PyImports,然后运行python -m unittest tests.test_abs
duozmo '16

7
我同意吉恩的观点。我希望有一种调试导入过程的机制,该机制会有所帮助。就我而言,我在同一目录中有两个文件。我正在尝试将一个文件导入另一个文件。如果该目录中有一个init .py文件,则会出现ValueError:尝试在非程序包中进行相对导入错误。如果删除init .py文件,则会收到一个错误,提示没有名为“ NAME”的模块错误。
user1928764 '17

就我而言,我在同一目录中有两个文件。我正在尝试将一个文件导入另一个文件。如果我在该目录中有一个init .py文件,则会收到ValueError:尝试在非程序包中进行相对导入错误。如果删除init .py文件,则会收到一个错误,提示模块“ NAME”错误。真正令人沮丧的是,我进行了此工作,然后删除了.bashrc文件,将PYTHONPATH设置为某种值,但现在却无法正常工作,这使我措手不及。
user1928764 '17

Answers:


443

是。您没有将其用作包装。

python -m pkg.tests.core_test

51
一个陷阱:请注意,结尾没有“ .py”!
mindthief 2012年

497
我没有任何的downvoters的,但我觉得这可能会使用相当多的更详细,因为这个问题和答案的普及。注意到诸如从哪个目录执行上述shell命令之类的东西,您需要__init__.py一路向下的事实以及__package__-modifying技巧(由BrenBarn在下面描述)需要允许对可执行脚本的这些导入(例如,使用shebang和这样做./my_script.py在Unix外壳)都会是有用的。对于我来说,要弄清楚或找到简明易懂的文档,整个问题都非常棘手。
Mark Amery 2014年

16
注意:您需要pkg从CLI调用此行的位置不在目录之外。然后,它应该按预期工作。如果你在里面pkg,你打电话python -m tests.core_test,它将无法正常工作。至少它不适合我。
Blairg23

94
认真地说,你能解释你的答案怎么回事吗?
Pinocchio

18
@MarkAmery几乎迷失了我的头脑,试图弄清所有这些是如何工作的,在项目中的相对导入中包含带有py文件的子目录,而这些子目录具有py文件,__init__.py但您仍然会收到ValueError: Attempted relative import in non-package错误消息。我会为某个地方的某人付出非常好的钱,以便最终用简单的英语解释所有这些工作原理。
AdjunctProfessorFalcon '16

635

详细阐述伊格纳西奥·巴斯克斯·阿布拉姆斯答案:

Python导入机制相对于__name__当前文件起作用。直接执行文件时,它没有通常的名称,但是具有"__main__"以它的名称命名。因此,相对进口无效。

您可以按照Igancio的建议使用该-m选项执行它。如果包的一部分要作为脚本运行,则还可以使用__package__属性告诉该文件在包层次结构中应具有的名称。

参见http://www.python.org/dev/peps/pep-0366/详细信息,。


55
花了一段时间让我意识到您不能python -m core_testtests子目录中运行-它必须来自父目录,或者必须将父目录添加到路径中。
2013年

3
@DannyStaple:不完全是。您可以__package__用来确保可执行脚本文件可以从同一包中相对导入其他模块。没有办法从“整个系统”中相对导入。我什至不知道为什么要这么做。
BrenBarn

2
我的意思是,如果__package__符号设置为“ parent.child”,那么您将能够导入“ parent.other_child”。也许我说得不太好。
Danny Staple 2013年

5
@DannyStaple:链接文档中描述了它的工作方式。如果您script.py在包中包含脚本pack.subpack,则将其设置__package__pack.subpack将允许您from ..module import something从中导入内容pack.module。请注意,如文档所述,您仍然必须在系统路径上具有顶级程序包。这已经是导入模块工作的方式。唯一要做的__package__就是让您也将这种行为用于直接执行的脚本。
BrenBarn

3
__package__在直接执行的脚本中使用,但是不幸的是,出现以下错误:“未加载父模块'xxx',无法执行相对导入”
mononoke

202

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__))))

35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))这也将起作用
2013年

26
from os import sys看起来像是在作弊:)
飞羊

3
@Piotr:它可能会被认为是更好的,因为它稍微更清楚地表明什么被追加到sys.path-该目录的父当前文件是英寸
马蒂诺

8
@flyingsheep:同意,我只使用常规代码import sys, os.path as path
martineau 2014年

10
仅供参考,为了在ipython笔记本中使用此代码,我将此答案调整为:import os; os.sys.path.append(os.path.dirname(os.path.abspath('.')))。然后import components.core对我有用,直接从笔记本的父目录中导入。
赛车Ta 2014年

195

这取决于您要如何启动脚本。

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

python tests/core_test.py

然后,由于在这种情况下'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

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

python -m pkg.tests.core_test

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

from ..components.core import GameLoopEvents

最后,您可以将两种方法混合使用,以便您的脚本无论调用方式如何都可以正常工作。例如:

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

3
如果我尝试使用pdb进行调试,该怎么办?因为您用于python -m pdb myscript.py启动调试会话。
丹尼2015年

1
@dannynjust-这是一个好问题,因为您不能有2个主要模块。通常,在调试时,我希望在要开始调试的第一点手动进入调试器。您可以通过将a import pdb; pdb.set_trace()插入代码(内联)来实现。
mgilson

3
insert代替会更好append吗?也就是说,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine

2
使用insert是相对导入语义的更好匹配,其中本地软件包名称优先于已安装软件包。特别是对于测试,您通常要测试本地版本,而不是已安装的版本(除非您的测试基础结构安装了受测试的代码,在这种情况下,不需要相对导入,并且不会出现此问题)。
Alex Dupuy

1
您还应该提到,作为模块运行时,您不能位于包含core_test的目录中(这太容易了)
Joseph Garvin


10

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

$$ py.test

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

from ..components.core import GameLoopEvents

但是,如果您将测试脚本作为主程序运行,那么事情将再次失败。因此,只需使用测试运行器。也许这也适用于其他测试运行程序,例如,nosetests但我尚未检查。希望这可以帮助。


9

我的快速解决方案是将目录添加到路径:

import sys
sys.path.insert(0, '../components/')

6
您的方法在所有情况下都行不通,因为'../'部分是从运行脚本的目录(core_test.py)中解析的。使用您的方法,您必须在运行core_test.py scritp之前先进行“测试”。
xyman

7

问题在于您的测试方法,

你试过了 python core_test.py

那么您将收到此错误 ValueError:尝试在非包中进行相对导入

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

因此,请从软件包源测试模块。

如果这是您的项目结构,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

cd pkg

python -m tests.core_test # dont use .py

或从外部pkg /

python -m pkg.tests.core_test

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

hi/
  hello.py
how.py

how.py

from .hi import hello

如果你想从hello.py导入

from .. import how

1
比被接受的答案好
GabrielBB

在示例中from .. import how,如何从“ how”文件中导入特定的类/方法。当我做相当于的事时,from ..how import foo我会得到“尝试超越顶级软件包的相对导入”
James Hulse

3

旧线程。我发现__all__= ['submodule', ...]__init__.py文件中添加,然后from <CURRENT_MODULE> import *在目标中使用可以正常工作。


3

您可以使用from pkg.components.core import GameLoopEvents,例如我使用pycharm,下面是我的项目结构图像,我只是从根包中导入,然后就可以了:

在此处输入图片说明


3
这对我没有用。您是否必须在配置中设置路径?
Mohammad Mahjoub,

3

正如Paolo所说,我们有2种调用方法:

1) python -m tests.core_test
2) python tests/core_test.py

它们之间的区别是sys.path [0]字符串。由于解释将在导入时搜索sys.path,因此我们可以使用tests/core_test.py

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

之后,我们可以使用其他方法运行core_test.py:

cd tests
python core_test.py
python -m core_test
...

注意,py36仅经过测试。


3

这种方法对我有用,并且比某些解决方案更混乱:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

父目录位于我的PYTHONPATH中,并且__init__.py父目录和此目录中都有文件。

上面的代码始终在python 2中有效,但是python 3有时会遇到ImportError或ModuleNotFoundError(后者在python 3.6中是新功能,是ImportError的子类),因此以下调整对我在python 2和3中均有效:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents


1

如果有人正在寻找解决方法,我偶然发现了一个。这里有一些背景。我想测试文件中的一种方法。当我从内部运行时

if __name__ == "__main__":

它总是抱怨相对进口。我尝试应用上述解决方案,但由于许多嵌套文件,每个文件都有多个导入,因此无法正常工作。

这就是我所做的。我刚刚创建了一个启动器,一个外部程序,它将导入必要的方法并调用它们。虽然这不是一个很好的解决方案,但它可以工作。


0

这是一种会惹恼所有人但效果很好的方法。在测试中运行:

ln -s ../components components

然后只需像往常一样导入组件。


0

这非常令人困惑,如果您使用的是像pycharm这样的IDE,那就更令人困惑了。对我有用的方法:1.进行pycharm项目设置(如果从VE或python目录运行python)2.定义的方式没有错。有时它与from folder1.file1导入类一起使用

如果它不起作用,请使用import folder1.file1。3.您的环境变量应在系统中正确提及或在命令行参数中提供。


-2

由于您的代码包含if __name__ == "__main__",而不会作为包导入,因此最好使用它sys.path.append()来解决问题。


我认为if __name__ == "__main__"您的文件中没有任何与导入相关的内容。
user48956 '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.