我正在创建一个包,并且此包中的模块在if __name__=='__main__':
块中包含用于测试目的的代码。但是我在这些模块中使用相对导入的尝试会导致错误。
我已经读过这个帖子和其他十亿人:Relative imports for the billionth time
在你将它标记为重复之前,如果我想要做的事情在Python3中是不可能的,那么我的问题是为什么它在Python2中起作用以及是什么促使决定在Python3中做出这样的麻烦?
这是我的示例Python项目:
mypackage
- module1.py
- module2.py
- __init__.py
__init__.py
和module2.py
是空的
module1.py
包含:
import module2
# module1 contents
if __name__=="__main__":
# Some test cases for the contents of this module
pass
这在Python2中工作正常。我可以从我计算机上任何地方的其他项目中导入module1,我也可以直接运行module1并运行if
块中的代码。
但是,此结构在Python3中不起作用。如果我尝试在其他地方导入模块,它会失败:
>>> from mypackage import module1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\_MyFiles\Programming\Python Modules\mypackage\module1.py", line 1, in <module>
import module2
ModuleNotFoundError: No module named 'module2'
所以我尝试将第一行更改为from . import module2
,并修复它以便我可以从任何地方成功导入模块。但是当我尝试直接将module1作为脚本运行时,我收到此错误:
Traceback (most recent call last):
File "C:/_MyFiles/Programming/Python Modules/mypackage/module1.py", line 1, in <module>
from . import module2
ImportError: cannot import name 'module2' from '__main__' (C:/_MyFiles/Programming/Python Projects/pgui/mypackage/module1.py)
我不想打开控制台并在每次处理模块时键入python -m myfile
并希望直接将其作为脚本运行。
我希望能够在不使用Python2中的相对导入将其父文件夹添加到PYTHONPATH的情况下处理模块
这些问题有更好的解决方法或解决方案吗?
根据Module documentation,对于__main__
模块,您必须使用绝对导入。
请注意,相对导入基于当前模块的名称。由于主模块的名称始终为“main”,因此用作Python应用程序主模块的模块必须始终使用绝对导入。
所以只需将module1.py
中的导入行更改为:
from mypackage import module2
其他一切都是一样的。
我最后遇到了类似的情况,直到我意识到模块和包导入应该如何工作之前,它给我带来了很多麻烦。
考虑以下结构
mydir
- project
- __init__.py
- module1.py
- module2.py
module1
和module2
的内容如下所示
module1.朋友
print("moudule1")
谋得了2.朋友
来自。导入模块1
print("Module 2")
if __name__ == '__main__':
print("Executed as script")
现在,如果我在包目录外打开一个repl并尝试进行导入,它就可以了
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from package import module2
Module 1
Module 2
>>> sys.path
['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/rbhanot/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages']
请注意sys.path
,因为您可以看到它包含我当前的目录作为第一项,这意味着我的所有导入将首先在我当前的目录中搜索。
现在,如果我进入包目录然后打开一个repl,并尝试进行相同的导入,看看会发生什么
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from . import module2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: cannot import name 'module2'
>>> import module2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/rbhanot/python-dotfiles/python3/modules-packages/mydir/package/module2.py", line 1, in <module>
from . import module1
ImportError: attempted relative import with no known parent package
>>> import module1
Module 1
>>>
因为你可以导入失败,失败的原因是当我尝试从sys.path
中的包python搜索导入模块以找到名为package
的任何包时,因为我找不到任何包,因此导入失败。但是导入module1是有效的,因为它位于当前目录中。
在包外面我可以执行脚本
python3 -m package.module2 2 ↵
Module 1
Module 2
Executed as script
虽然我可以执行脚本,但这不是它应该如何使用。记住包是需要共享的代码库,不应该有任何可以通过命令行直接执行的代码。包中的包和模块只是要导入,然后在导入后,您可以编写脚本,通过在其中放入__name__
子句,通过命令行执行。
Python包不仅仅是一个你坚持代码的文件夹,而且导入行为不仅仅取决于你的代码所在的文件夹。
直接运行文件时,您不会将其作为程序包的一部分运行。包级别初始化不会运行,Python甚至不识别包的存在。在Python 2中,隐式相对导入的存在意味着一个简单的import module2
将解析为绝对导入或隐式相对导入,隐藏问题,但导入结构仍然被破坏。在Python 3上,隐式相对导入已经消失(有充分理由),因此问题立即可见。
直接通过文件名运行包的子模块不能很好地工作。这些天,我相信标准是要么使用-m
,要么使用调用子模块功能的顶级入口点脚本。
无论如何,有一种方法可以让文件名运行,但它有很多样板。 PEP 366的设计者似乎打算用__package__ = 'appropriate.value'
赋值使相对导入正常工作,但即使你修复了导入路径,这实际上还不够。您还必须手动初始化父包,否则只要您尝试运行相对导入,就会得到“SystemError:父模块'foo'未加载,无法执行相对导入”。完整的样板看起来更像
import os.path
import sys
if __name__ == '__main__' and __package__ is None:
__package__ = 'mypackage'
right_import_root = os.path.abspath(__file__)
for i in range(__package__.count('.') + 2):
right_import_root = os.path.dirname(right_import_root)
# sys.path[0] is usually the right sys.path entry to replace, but this
# may need further refinement in the presence of anything else that messes
# with sys.path
sys.path[0] = right_import_root
__import__(__package__)
这类似于未来的导入,但在任何依赖于您的包的导入之前。
我将这个样板包装在一个可重用的函数中(使用堆栈操作来访问调用者的全局变量),除非你试图将该函数放在项目的某个地方,否则在修复你的函数之前你将无法导入该函数。导入情况,您需要该功能。它可能作为可安装的依赖项工作。