如何在带有if __name __ ='__ main__'块的Python3中使用相对导入?

时间:2019-02-02 03:18:43

标签: python python-3.x python-import relative

我正在制作一个软件包,该软件包中的模块在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并想直接将其作为脚本运行。

我希望能够通过使用相对导入(如在Python2中)将模块的父文件夹不添加到PYTHONPATH中

对于这些问题是否有更好的解决方法或解决方案?

3 个答案:

答案 0 :(得分:2)

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

  

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

因此,只需将module1.py中的导入行更改为:

from mypackage import module2

其他所有内容都保持不变。

答案 1 :(得分:0)

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

这是在诸如将来的进口之类的东西之后,但是在任何依赖于您的包装的进口之前。

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

答案 2 :(得分:0)

我结束了在类似的情况,并直到我意识到模块和组件的进口应该如何去解决它困扰了我很多东西。

考虑以下结构

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

module1module2的内容如下所示

module1.py

print("moudule1")

moudle2.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
>>>

您可能会导致导入失败,失败的原因是,当我尝试从sys.path中的包python搜索导入模块以查找名称为package的任何包时,因为我找不到任何包,因此导入失败。但是导入module1可以,因为它可以在当前目录中找到。

封装外部我可以执行脚本作为

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

尽管我可以执行脚本,但是这不是应该使用的方式。请记住,程序包是需要共享的代码库,并且不应包含可通过命令行直接执行的任何代码。包装内包和模块是为了将刚刚导入,然后导入后,你可以写你的脚本,通过把__name__子句中它通过命令行执行。