我正试图遵循PEP 328,目录结构如下:
1 2 3 4 5 6 7 8
| pkg/
__init__.py
components/
core.py
__init__.py
tests/
core_test.py
__init__.py |
在core_test.py中,我有以下进口声明
1
| from ..components.core import GameLoopEvents |
但是,当我运行时,会得到以下错误:
1 2 3 4 5
| 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 |
在四处搜索时,我发现"相对路径即使与uuu init_uuy.py"和"从相对路径导入模块"都不起作用,但它们没有帮助。
我这里有什么东西不见了吗?
- 我对构建unittest项目的各种方法也非常困惑,所以我写了这个相当详尽的示例项目,它涵盖了模块的深层嵌套、相对和绝对导入(在这里工作和不工作)、包内的相对和绝对引用,以及类的单级、双级和包级导入。锿。帮我把事情弄清楚!
- 我不能让你的测试工作。当我运行它们时,继续获取no module named myimports.foo。
- @blairg23我猜预期的调用是将cd转换成PyImports,然后运行python -m unittest tests.test_abs。
- Python大约是我的第20种语言,我必须说它是处理导入时遇到的最糟糕的语言。我从未见过其他语言对这个话题有如此多的困惑。
- 我同意吉恩的观点。我希望有一种机制可以调试导入过程,这会有点帮助。在我的例子中,我在同一个目录中有两个文件。我正在尝试将一个文件导入另一个文件。如果该目录中有init.py文件,我会得到一个valueerror:尝试在非包错误中进行相对导入。如果我删除init.py文件,那么会得到一个名为'name'错误的无模块错误。
- 在我的例子中,我在同一个目录中有两个文件。我正在尝试将一个文件导入另一个文件。如果该目录中有init.py文件,我会得到一个valueerror:尝试在非包错误中进行相对导入。如果我删除init.py文件,那么会得到一个名为'name'错误的无模块错误。真正让人沮丧的是,我做了这个工作,然后我删除了.bashrc文件,它将pythonpath设置为某个值,但现在它不工作了。
- @用户1928764:这些错误确实令人沮丧。我想你的意思是"__init__.py文件"。
详细说明伊格纳西奥·瓦兹奎兹·艾布拉姆斯的回答:
python导入机制相对于当前文件的__name__工作。当您直接执行一个文件时,它没有通常的名称,而是使用"__main__"作为它的名称。所以相对进口不起作用。
正如igancio建议的那样,您可以使用-m选项来执行它。如果您的包中有一部分打算作为脚本运行,那么您也可以使用__package__属性来告诉该文件它在包层次结构中应该具有什么名称。
详情请参见http://www.python.org/dev/peps/pep-0366/。
- 我花了一段时间才意识到你不能从tests子目录中运行python -m core_test--它必须来自父目录,或者你必须将父目录添加到路径中。
- 因此,我是否可以使用包来确保可执行脚本文件能够从系统的其余部分进行相对导入,而不管它们在层次结构中的级别如何?这确实非常有用。
- @丹尼:不完全是。您可以使用__package__来确保可执行脚本文件可以相对地从同一个包中导入其他模块。没有办法相对地从"整个系统"中导入。我甚至不知道你为什么要这么做。
- 我的意思是如果__package__符号设置为"parent.child",那么您就可以导入"parent.other_child"。也许我说得不太好。
- @Dannystaple:好吧,链接文档中描述了它的工作方式。如果您在pack.subpack包中有一个脚本script.py,那么将其设置为__package__到pack.subpack将允许您执行from ..module import something以从pack.module导入某些内容。请注意,正如文档所说,您仍然必须在系统路径上具有顶级包。这已经是导入模块的工作方式了。__package__所做的唯一一件事就是允许您对直接执行的脚本使用这种行为。
- 在Brenbarn的PEP-0366链接文档中没有样本代码。在哪里可以找到示例代码?
- @艾莉森:做什么的示例代码?PEP中有一个小片段解释了如何设置__package__。
- 但是没有上下文说明这段代码在brenbarn的归属。它是否在您要导入的文件中?在执行导入的文件中?它在相关文件中的位置?
- @Alison:它在这里说:"为了在直接执行模块时允许相对导入,在第一个相对导入语句之前需要类似于以下内容的样板文件。"我认为很明显,在模块尝试任何相对导入之前,您将它放在正在直接执行的模块中。
- 我在直接执行的脚本中使用了__package__,但不幸的是,我得到了以下错误:"父模块'xxx'未加载,无法执行相对导入。"
对。你不能把它当作包裹使用。
1
| python -m pkg.tests.core_test |
- A明白了:注意结尾没有"py"!
- 我不是投反对票的人,但考虑到这个问题和答案的受欢迎程度,我觉得这可能需要更多的细节。注意到执行上述shell命令的目录中的内容,您需要一路向下使用__init__.py,以及__package__修改技巧(下面由brenbarn描述),以允许这些可执行脚本的导入(例如使用shebang和在unix shell上执行./my_script.py)都是有用的。整个问题对于我来说很难找到或找到简明易懂的文档。
- 下面有一个精心设计的最新答案,链接到文档,正如赞成的评论所建议的那样。
- 注意:在从cli调用此行时,您需要在目录pkg之外。然后,它应该按预期工作。如果你在pkg里面,你叫python -m tests.core_test,它就不起作用了。至少我没有。
- 例如,如果在components dir中运行脚本,则此操作不起作用。
- 我将它与cd结合到项目的父目录中。cd /path/to/project && python -m project.module.submodule。如果您要在本地系统上多次测试这个问题,那么您最终可能需要添加&& cd ~。
- 当他们设计语言时,为什么不让"python pkg/test/core_test.py"和"python-m pkg.test.core_test"做同样的事情呢?为什么当您运行任何脚本时,它们会故意破坏相对导入?
- @乔纳坦雷:因为让他们做同样的事情会让执行变得模棱两可,那么让python /usr/share/someapp/somescript.py成为usr.share.someapp.somescript又有什么意义呢?
- 说真的,你能解释一下你的答案是什么吗?
- @Markamery几乎忘记了我试图摸索这一切是如何工作的,在一个项目中,相对导入的子目录包含了带有__init__.py文件的py文件,但您仍然得到ValueError: Attempted relative import in non-package错误。我会在某个地方为某人支付一大笔钱,最终用简单的英语解释这一切是如何运作的。
- 与python中常见问题解答的答案相反,此答案不提供正在发生的事情以及从何处执行此命令的线索。
- 用于在Jupyter笔记本上运行%run-m
- 我不知道作为一个模块运行它如何帮助@skytrader从另一个脚本中导入问题…
如果将当前目录附加到sys.path中,可以直接使用import components.core:
1 2 3
| if __name__ == '__main__' and __package__ is None:
from os import sys, path
sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) |
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))这也会起作用。
- from os import sys看起来像是作弊。
- @Ajay和你的更好是因为什么?
- @piotr:它可能会被认为更好,因为它稍微清楚地显示了当前文件所在目录的父目录sys.path的附加内容。
- @马蒂诺,我认为你是对的。)
- @彼得亚雷:啊,是的,我必须同意,江户十一〔五〕会更清楚,加上一句评论,就完美了。
- @弗林希普:同意,我就用普通的import sys, os.path as path。
- 仅供参考,为了在ipython笔记本中使用,我将这个答案改为:import os; os.sys.path.append(os.path.dirname(os.path.abspath('.')))。然后,一个直截了当的import components.core对我有效,根据需要从笔记本的父目录导入。
- 非常适合嵌套使用dirname函数。以前没见过。
- from os import sys是如何工作的?os是sys模块外的一个模块。
- 我可以再加一个技巧吗…对于类似的问题,我尝试了所有可以找到的建议,最后发现我有一些导致冲突的旧.pyc文件。因此,如果有人尝试上述解决方案但失败,请检查是否需要删除.pyc文件。
这取决于您希望如何启动脚本。
如果您想以一种经典的方式从命令行启动UnitTest,那就是:
1
| python tests/core_test.py |
然后,因为在本例中,"components"和"tests"是兄弟文件夹,所以可以使用sys.path模块的insert或append方法导入相对模块。类似:
1 2 3 4
| import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents |
否则,可以使用"-m"参数启动脚本(请注意,在本例中,我们讨论的是一个包,因此不能提供".py"扩展名),即:
1
| python -m pkg.tests.core_test |
在这种情况下,您只需使用相对导入即可:
1
| from ..components.core import GameLoopEvents |
最后,您可以混合使用这两种方法,这样无论脚本如何调用,它都可以工作。例如:
1 2 3 4 5 6 7 8
| 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 |
- 如果我尝试使用PDB进行调试,该怎么做?因为您使用python -m pdb myscript.py启动调试会话。
- @丹尼斯特——这是个好问题,因为你不能有两个主要模块。通常,在调试时,我希望在第一个要开始调试的点手动插入调试程序。您可以通过将import pdb; pdb.set_trace()插入代码(内联)来实现这一点。
- 用insert代替append更好吗?也就是说,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))。
- 使用insert更好地匹配相对导入语义,其中本地包名称优先于已安装的包。尤其是对于测试,您通常希望测试本地版本,而不是已安装的版本(除非您的测试基础结构安装了测试中的代码,在这种情况下,不需要进行相对导入,并且不会出现此问题)。
- 您还应该提到,当您作为模块运行时,不能在包含核心测试的目录中(这太容易了)
- 这太有用了!谢谢!
- 救了我的命!谢谢!
在core_test.py中,执行以下操作:
1 2 3
| import sys
sys.path.append('../components')
from core import GameLoopEvents |
如果您的用例是用于运行测试的,并且它确实是这样的,那么您可以执行以下操作。不要以python core_test.py的形式运行测试脚本,而是使用一个测试框架,如pytest。然后在命令行中输入
将运行目录中的测试。这就解决了@brenbarn指出的__name__是__main__的问题。接下来,将一个空的__init__.py文件放入测试目录,这将使测试目录成为包的一部分。那你就能做到了
1
| from ..components.core import GameLoopEvents |
但是,如果您将测试脚本作为主程序运行,那么事情将再次失败。所以只要使用测试运行程序。也许这也适用于其他测试运行者,如nosetests,但我没有检查过。希望这有帮助。
我的快速修复方法是将目录添加到路径:
1 2
| import sys
sys.path.insert(0, '../components/') |
- 您的方法在所有情况下都不起作用,因为"../"部分是从运行脚本的目录(core_test.py)解析的。使用您的方法,在运行core_test.py scritp之前,您必须cd到"tests"。
您可以使用from pkg.components.core import GameLoopEvents,例如我使用pycharm,下面是我的项目结构图,我只是从根包导入,然后它就工作了:
老线程。我发现在_初始化文件,然后在目标中使用from import *就可以了。
正如paolo所说,我们有两个调用方法:
1 2
| 1) python -m tests.core_test
2) python tests/core_test.py |
它们之间的一个区别是sys.path[0]字符串。由于在执行导入时,解释将搜索sys.path,因此我们可以使用tests/core_test.py:
1 2 3 4 5 6
| 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:
1 2 3 4
| cd tests
python core_test.py
python -m core_test
... |
注:仅PY36测试。
试试这个
1 2
| import components
from components import * |
如果有人在找工作,我偶然发现了一个。这里有一点背景。我想测试一下我在文件中的一种方法。当我从内部运行它时
1
| if __name__ =="__main__": |
它总是抱怨相对进口。我尝试应用上面的解决方案,但没有成功,因为有许多嵌套文件,每个都有多个导入。
我就是这么做的。我刚刚创建了一个启动程序,一个外部程序,可以导入必要的方法并调用它们。不过,这不是一个很好的解决方案,它是有效的。