如何在Python3中使用if __name __ ='__ main__'块进行相对导入?

问题描述 投票:3回答:3

我正在创建一个包,并且此包中的模块在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的情况下处理模块

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

python python-3.x python-import relative
3个回答
3
投票

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

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

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

from mypackage import module2

其他一切都是一样的。


0
投票

我最后遇到了类似的情况,直到我意识到模块和包导入应该如何工作之前,它给我带来了很多麻烦。

考虑以下结构

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

module1module2的内容如下所示

module1.朋友

print("moudule1")

谋得了2.朋友

来自。导入模块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__子句,通过命令行执行。


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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.