我试图遵循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。
这种方法对我有效,比一些解决方案更不杂乱:
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
问题在于您的测试方法,
您尝试了python core_test.py
那么你会得到这个错误ValueError:尝试在非包中进行相对导入
原因:您正在从非包源测试您的包。
所以从包源测试模块。
如果这是您的项目结构,
pkg/
__init__.py
components/
core.py
__init__.py
tests/
core_test.py
__init__.py
cd软件包
python -m tests.core_test # dont use .py
或从外部pkg/
python -m pkg.tests.core_test
仅有一个的如果要从同一目录中的文件夹导入。每后退一步,再加一步。
hi/
hello.py
how.py
在how.py中
from .hi import hello
如果您想从hello.py导入how
from .. import how
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中的相对导入,了解详细说明以及如何完成。
我也遇到过类似的问题,作为一名软件工程师,我认为这里提出的一些解决方案并不理想。如果您想要相对导入,则不应尝试/except,然后有时进行绝对导入。此外,要运行程序,您不必更改sys.path。
此外,程序应该始终运行,独立于当前的工作目录和启动方式。
因此,我创建了一个新的实验导入库:ultraimport它允许基于文件系统的导入,无论您如何运行代码。
从最初的问题中,您可以将core_test.py更改为
import ultraimport
GameLoopEvents = 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的examples文件夹中。
由于图书馆是实验性的,我对反馈感兴趣。它对我有效,但尚未得到广泛测试。