如何在 Python3 中使用 if __name__='__main__' 块中的相对导入?

How can I use relative importing in Python3 with an if __name__='__main__' block?

我正在制作一个包,这个包中的模块在 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,并修复了它,以便我可以从任何地方成功导入模块。但是当我直接尝试 运行ning 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

中的相对导入

这些问题有更好的解决方法或解决方案吗?

根据 Module documentation,对于 __main__ 模块,您必须使用绝对导入。

Note that relative imports are based on the name of the current module. Since the name of the main module is always "main", modules intended for use as the main module of a Python application must always use absolute imports.

所以只需将 module1.py 中的导入行更改为:

from mypackage import module2

其他一切保持不变。

Python 包不仅仅是您将代码粘贴到的文件夹,而且导入行为不仅仅取决于您将代码粘贴到哪个文件夹。

当您直接 运行 您的文件时,您并不是 运行 将其作为包的一部分。包级初始化不 运行,Python 甚至不识别包的存在。在 Python 2 上,隐式相对导入的存在意味着裸 import module2 将解析为绝对导入或隐式相对导入,隐藏了问题,但导入结构仍然被破坏。在 Python 3 上,隐式相对导入消失了(有充分的理由),因此问题立即可见。

运行 直接通过文件名创建包的子模块效果不佳。现在,我认为标准是使用 -m,或者使用调用子模块功能的顶级入口点脚本。

有一种方法可以使 运行-by-filename 正常工作,但它有很多样板。 PEP 366 的设计者似乎打算通过 __package__ = 'appropriate.value' 赋值来使相对导入正常工作,但实际上这还不够,即使您修复了导入路径。您还必须手动初始化父包,否则您将在尝试 运行 相对导入时立即获得 "SystemError: Parent module 'foo' not loaded, cannot perform relative import"。完整的样板看起来更像

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__)

这是在诸如未来导入之类的东西之后,但在任何依赖于您的包的导入之前。

我会将这个样板包装在一个可重用的函数中(使用堆栈操作来访问调用者的全局变量),除非你试图将该函数放在你的项目中的某个地方,否则你将无法导入该函数,直到您已经修复了导入情况,您需要该功能来完成。它可能作为可安装的依赖项工作。

我最终遇到了类似的情况,这让我很困扰,直到我意识到模块和包导入应该如何工作。

考虑以下结构

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 中搜索以查找名称为 [=20= 的任何包],因为我找不到任何 ,因此导入失败。但是导入 module1 是有效的,因为它在当前目录中找到。

在包外我可以执行脚本

python3 -m package.module2                                                                              2 ↵
Module 1
Module 2
Executed as script

虽然我可以执行脚本,但这不是它应该如何使用的。请记住,包是需要共享的代码库,不应包含任何可通过命令行直接执行的代码。包内的包和模块只是导入,然后在导入后您可以编写通过命令行执行的脚本,方法是在其中放置 __name__ 子句。