繁体   English   中英

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

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

我正在制作一个包,并且这个包中的模块内部有代码if __name__=='__main__':用于测试目的的块。 但是我尝试在这些模块中使用相对导入会导致错误。

我已经阅读了这个帖子和其他十亿人: 第 10 亿次的相对进口

在将其标记为重复之前,如果我想做的事情在 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 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 中的相对导入来处理模块

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

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

请注意,相对导入基于当前模块的名称。 由于主模块的名称始终为“ main ”,因此用作 Python 应用程序主模块的模块必须始终使用绝对导入。

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

from mypackage import module2

其他一切都保持不变。

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

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

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

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

考虑以下结构

mydir
- project
  - __init__.py
  - module1.py
  - module2.py

module1module2内容如下所示

模块1.py

print("moudule1")

模块2.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任何package ,因为我找不到任何包,因此导入失败。 但是导入 module1 是有效的,因为它是在当前目录中找到的。

在包外,我可以将脚本执行为

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM