我试图遵循PEP 328,其目录结构如下:

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

在core_test.py中,我有以下import语句

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也无法工作”和“从相对路径导入模块”,但它们都没有帮助。

这里有什么我遗漏的吗?


当前回答

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

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

python tests/core_test.py

然后,因为在这种情况下,“components”和“tests”是同级文件夹,所以可以使用sys.path模块的insert或append方法导入相关模块。类似于:

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

其他回答

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

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。

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

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

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

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

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

而不是

from ..components.core import GameLoopEvents

仅仅

from pkg.components.core import GameLoopEvents

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

python -m pkg.tests.core_test

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

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

python <main_module>.py

PEP 338中有明确规定。

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

原因

Python Bug Tracker Issue1510172:绝对/相对导入不起作用?

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

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