如何修复“在非包中尝试相对导入”即使使用__init__. py

我试图遵循PEP 328,使用以下目录结构:

pkg/__init__.pycomponents/core.py__init__.pytests/core_test.py__init__.py

core_test.py中,我有以下导入语句

from ..components.core import GameLoopEvents

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

tests$ python core_test.pyTraceback (most recent call last):File "core_test.py", line 3, in <module>from ..components.core import GameLoopEventsValueError: Attempted relative import in non-package

搜索我发现“相对路径不工作,甚至与__init__. py”和“从相对路径导入模块”,但他们没有帮助。

有什么我错过的吗?

616148 次浏览

是的。你没有把它当作一个包裹。

python -m pkg.tests.core_test

详细说明伊格纳西奥·巴斯克斯-艾布拉姆斯的答案:

Python导入机制相对于当前文件的__name__工作。当您直接执行一个文件时,它没有通常的名称,而是以"__main__"作为其名称。所以相对导入不起作用。

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

详情见http://www.python.org/dev/peps/pep-0366/

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

if __name__ == '__main__' and __package__ is None:from os import sys, pathsys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

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

如果你想以经典的方式从命令行启动您的UnitTest,那就是:

python tests/core_test.py

然后,由于在这种情况下组件测试是兄弟文件夹,您可以使用sys.path模块的插入追加方法导入相对模块。类似:

import sysfrom os import pathsys.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 sysfrom os import pathsys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )from components.core import GameLoopEventselse:from ..components.core import GameLoopEvents

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

$$ py.test

这将在您的目录中运行测试。这解决了@BrenBarn指出的__name____main__的问题。接下来,将一个空的__init__.py文件放入您的测试目录,这将使测试目录成为您包的一部分。然后您将能够

from ..components.core import GameLoopEvents

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

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

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

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

import syssys.path.append('../components')from core import GameLoopEvents

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

if __name__ == "__main__":

它总是抱怨相对导入。我尝试应用上述解决方案,但未能成功,因为有许多嵌套文件,每个文件都有多个导入。

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

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

试试这个

import componentsfrom components import *

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

在此处输入图片描述

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

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

它们之间的一个区别是sys.path[0]字符串。从执行导入时,解释器将搜索sys.path开始,我们可以使用tests/core_test.py

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

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

cd testspython core_test.pypython -m core_test...

注意,py36仅测试。

这种方法对我很有效,比一些解决方案更不混乱:

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

父目录在我的PYTHONPATH中,父目录和这个目录中有__init__.py个文件。

上面的方法在python 2中总是有效的,但是python 3有时会遇到一个重要错误或ModuleNotFoundError(后者在python 3.6中是新的,并且是重要错误的子类),所以下面的调整对我来说在python 2和3中都有效:

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

因为你的代码包含if __name__ == "__main__",它不能作为包导入,所以你最好使用sys.path.append()来解决这个问题。

问题在于你的测试方法,

你试过python core_test.py

然后你会得到这个错误ValueError:尝试在非包中相对导入

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

因此,从包源测试您的模块。

如果这是你的项目结构,

pkg/__init__.pycomponents/core.py__init__.pytests/core_test.py__init__.py

cd pkg

python -m tests.core_test # dont use .py

或从外部pkg/

python -m pkg.tests.core_test

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

hi/hello.pyhow.py

how.py

from .hi import hello

如果你想导入如何从hello.py

from .. import how

这里有一种方法会惹恼每个人,但效果很好。在测试运行中:

ln -s ../components components

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

这是非常令人困惑的,如果你使用的是IDE,就会更令人困惑。什么对我有用:1.进行py魅力项目设置(如果您从VE或python目录运行python)2.你定义的方式没有错。有时它适用于从folder1.file1导入类

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

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

而不是

from ..components.core import GameLoopEvents

只是

from pkg.components.core import GameLoopEvents

从pkg的父级运行时,请使用以下命令

python -m pkg.tests.core_test

对我来说,只有这样才有效:我必须显式地将的值设置为父目录,并将父目录添加到sys.path

from os import pathimport sysif __package__ is None:sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )__package__= "myparent"
from .subdir import something # the . can now be resolved

我现在可以直接使用python myscript.py运行我的脚本。

python <main module>.py不适用于相对导入

问题是当您从命令行运行__main__模块时,相对进口不起作用

python <main_module>.py

这是在PEP 338中明确指出的。

2.5b1的发布显示了这个PEP和PEP 328-显式相对导入不适用于主模块之间令人惊讶的(尽管回想起来很明显)交互。这是因为相对导入依赖于__name__来确定当前模块在包层次结构中的位置。在主模块中,__name__的值总是'__main__',所以显性相对导入总是会失败(因为它们只适用于包内的模块)。

原因

这个问题实际上并不是-m开关独有的。问题是相对导入基于__name__,而在主模块中,__name__总是具有值__main__。因此,相对导入目前无法从应用程序的主模块正常工作,因为主模块不知道它在Python模块命名空间中的真正位置(对于通过-m开关执行的主模块,这至少在理论上是可以修复的,但直接执行的文件和交互式解释器完全不走运)。

要进一步了解,请参阅Python 3中的相对导入以获取详细说明以及如何完成它。

我也遇到过类似的问题,作为一名软件工程师,我认为这里建议的一些解决方案并不理想。如果你想要相对导入,你不应该尝试/除了然后有时做绝对导入。此外,要运行程序,你不应该改变sys.path.

此外,程序应该始终工作,独立于您当前的工作目录,独立于您如何启动它。

因此,我创建了一个新的、实验性的导入库:超导入它允许基于文件系统的导入,无论您如何运行代码。

从最初的问题,你会把你的core_test.py改成这样

import ultraimportGameLoopEvents = ultraimport('__dir__/../components/core.py', 'GameLoopEvents')print(GameLoopEvents)

它总是会找到它,无论你如何运行你的测试。

$ python -m tests.core_test<class 'core.GameLoopEvents'>python ./tests/core_test.py<class 'core.GameLoopEvents'>

我还将此示例放入git repo中的示例文件夹中。

由于图书馆是实验性的,我对反馈很感兴趣。它对我有用,但还没有被广泛测试。

如果您的项目结构看起来像这样:

   project|| --- module1|      ||      file1.py||-----module2|     ||     file2.py

您将从file2.py导入file1.py,您可以在file2.py:

import syssys.path.append('.')
import file2

我仍然不知道为什么和如何,但它对我有效。