如何在 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__.pymodule2.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__ 模块,您必须使用绝对导入。

请注意,相对导入基于当前模块的名称。由于主模块的名称始终为“ma​​in”,因此用作 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

module1module2 的内容如下所示

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?

(转)Python中如何理解if __name__ == '__main__'

如何在 R 中保存循环(使用 if 语句)的结果?

如何在我的元类中获取类的父类?

[Python3]循环控制

python3 _笨方法学Python_日记_DAY6