如何在 Python3 中使用带有 if __name__='__main__' 块的相对导入?
Posted
技术标签:
【中文标题】如何在 Python3 中使用带有 if __name__=\'__main__\' 块的相对导入?【英文标题】:How can I use relative importing in Python3 with an if __name__='__main__' block?如何在 Python3 中使用带有 if __name__='__main__' 块的相对导入? 【发布时间】:2019-06-26 15:08:45 【问题描述】:我正在制作一个包,这个包中的模块在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
并希望直接将其作为脚本运行。
我希望能够在不将其父文件夹添加到 PYTHONPATH 的情况下通过使用 Python2 中的相对导入来处理模块
有没有更好的解决方法或解决方案来解决这些问题?
【问题讨论】:
“我不想每次在处理模块时都打开控制台并输入python -m myfile
,并希望直接将其作为脚本运行。” - 而不是打开控制台并输入python myfile.py
?您目前如何运行您的文件? (对于包子模块也是python -m packagename.modulename
,而不是python -m modulename
。)
我猜其他人会告诉你正确的 python 方式,但是,你可以只使用一个`try ... except ImportError`来导入它的工作方式。 Dive Into Python 有一个example。
@user2357112 目前我正在通过 PyCharm IDE 运行我的文件,方法是单击 GUI 中的运行按钮。我意识到我可以将 -m 添加到配置中,但这似乎很麻烦,我希望我的代码在正常运行时能够正常运行,例如在 Python2 中。而且我想把它发送给某人使用,而不必警告他们并解释如何运行它,以避免相对导入失败的陷阱,main 块出现神秘错误。跨度>
@glumplum 谢谢,如果没有“正确”的方法,这似乎是一个可行的解决方法。
这能回答你的问题吗? Python3 correct way to import relative or absolute?
【参考方案1】:
根据Module documentation,对于__main__
模块,您必须使用绝对导入。
请注意,相对导入基于当前模块的名称。由于主模块的名称始终为“main”,因此用作 Python 应用程序主模块的模块必须始终使用绝对导入。
所以只需将module1.py
中的导入行更改为:
from mypackage import module2
其他一切都保持不变。
【讨论】:
你的代码有错误吗?module2.py
作为__init__.py
的第一行给出了一个未解决的引用错误。删除它,我仍然在 Python3 中遇到与以前相同的错误。
哦,是的,我现在似乎遇到了同样的问题。我找到了解决方案;将更新我的答案。对此感到抱歉。
谢谢,但这仍然需要将包的父文件夹添加到 PYTHONPATH,否则会失败。这意味着当我第一次编写代码时,我必须使用相对导入,然后一旦我决定将其设为模块,我就将所有导入语句重构为绝对?这也意味着我的代码对于将我的包作为 zip 下载的任何人都失败,除非他们首先将其添加到他们自己的 PYTHONPATH 中。因此,虽然不使用相对导入确实可以使代码正常工作,但这并不是我的 OP 关于如何让相对导入像在 Python2 中那样工作的答案。
添加__name__ == "__main__"
检查是否仅用于测试?理想情况下,如我所见,您将只有一个 main
模块,这将是您的代码的起点。对于测试,您可以使用不同的方法。这样你就可以在除main
模块之外的任何地方使用相对导入。此外,如果您打算将其作为 zip 文件分发,则您不希望代码有多个入口点(除非有特定原因这样做)。
这很有趣。我没有使用过 PyQt5,但我对使用 Tkinter 的一个项目有类似的要求。我们确实有完全相同的场景,我们使用主检查进行快速测试。我刚刚挖掘了那个项目——看起来我们已经把它分成了多个包,并使用绝对导入来从不同的模块导入特定的类——比如from package.module import class
。现在我不确定是否有更好的方法。【参考方案2】:
Python 包不仅仅是您将代码放入的文件夹,导入行为不仅仅取决于您将代码放入的文件夹。
当您直接运行文件时,您不会将其作为包的一部分运行。包级初始化不运行,Python 甚至不识别包的存在。在 Python 2 上,隐式相对导入的存在意味着裸 import module2
将解析为绝对导入或隐式相对导入,隐藏了问题,但导入结构仍然被破坏。在 Python 3 上,隐式相对导入消失了(有充分的理由),因此问题立即可见。
直接通过文件名运行一个包的子模块并不能很好地工作。这些天来,我相信标准是要么使用-m
,要么使用调用子模块功能的***入口点脚本。
无论如何,有一种方法可以让按文件名运行,但它有很多样板。 PEP 366 的设计者似乎打算为 __package__ = 'appropriate.value'
赋值以使相对导入正常工作,但这实际上还不够,即使您修复了导入路径。您还必须手动初始化父包,否则您将在尝试运行相对导入时立即收到“系统错误:未加载父模块'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__)
这在未来导入之类的东西之后,但在依赖于您的包的任何导入之前。
我会将这个样板封装在一个可重用的函数中(使用堆栈操作来访问调用者的全局变量),除非您尝试将该函数放在项目中的某个位置,否则您将无法导入该函数,直到您已经修复了您需要该功能执行的导入情况。它可以作为可安装的依赖项。
【讨论】:
【参考方案3】:我最终遇到了类似的情况,这让我很困扰,直到我意识到模块和包导入应该如何工作。
考虑以下结构
mydir
- project
- __init__.py
- module1.py
- module2.py
module1
和 module2
的内容如下所示
module1.py
print("moudule1")
module2.py
从 .导入模块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
>>>
如您所见,导入失败,失败的原因是当我尝试从包中导入模块时,python 在sys.path
中搜索以找到名称为package
的任何包,因为我找不到任何包,因此导入失败。但是导入 module1 是可行的,因为它是在当前目录中找到的。
在包外我可以执行脚本
python3 -m package.module2 2 ↵
Module 1
Module 2
Executed as script
虽然我可以执行脚本,但这不是它应该使用的方式。请记住,包是需要共享的代码库,不应包含任何可通过命令行直接执行的代码。包中的包和模块只是被导入,然后在导入后您可以编写脚本,通过在其中添加__name__
子句来通过命令行执行。
【讨论】:
以上是关于如何在 Python3 中使用带有 if __name__='__main__' 块的相对导入?的主要内容,如果未能解决你的问题,请参考以下文章
如何在驻留在不同路径的包上运行带有mod选项“-m”的Python3?