python的相对引用问题

背景

现象

遇到一个坑,import相对路径引起的。
我有一个如下的文件结构:

test/
    __init__.py # 此文件为空,代表test是一个package
    m1.py
    m2.py

m1.py文件内容如下:

# m1.py
a = 1

我想在m2.py文件中使用m1的变量,内容如下:

# m2.py
from .m1 import a

b = a + 1

if __name__ == '__main__':
    print(b)

此时运行m2.py会报如下错误:

itscs-MacBook-Pro:test itsc$ python m2.py 
Traceback (most recent call last):
  File "m2.py", line 1, in <module>
    from .m1 import a
ModuleNotFoundError: No module named '__main__.m1'; '__main__' is not a package

原因

python的两中文件执行方式

python可以将文件以两种方式执行:

python test/m2.py 
python -m test.m2 

此处为第一种,执行python m2.py的意思是将m2.py当做脚本执行。

相对导入机制

机制1:

python的模块(module)有一个__package__属性,如果这个属性存在并且有值,相对导入(import)就会基于这个属性去导入,而不是基于__name__属性,参见PEP 0366。使用脚本方式执行时,__package__属性值为None,那么将会基于__name__属性去做相对导入;而执行python -m test.m2时,__package__属性值为模块所在包路径,此处是:test,此时将通过该test去做相对导入。

机制2:

每一个模块都有一个__name__属性,它代表了「模块名」,例如此处的m2,当运行python m2.py时:

模块里的代码会被执行,就好像你导入了模块一样,但是 __name__ 被赋值为 __main__

所以报错里面提示的是__main__.m1找不到,而不是__m2__.m1找不到。

以上两个机制解释了为什么报错。首先,使用脚本方式执行时__package__属性值为None,那么将会基于__name__属性去做相对导入,而此时的__name__ 被赋值为 __main__,使用__main__去做相对导入是会失败的,应为它和m1并没有构成相对关系。

附:python文档中有这样一句解释:

请注意,相对导入是基于当前模块的名称进行导入的。由于主模块的名称总是 "__main__" ,因此用作Python应用程序主模块的模块必须始终使用绝对导入。

解决办法

根据上面的原因分析,有两种解决办法:

1.使用相对路径,使用带-m参数方式调用。

2.使用绝对路径,使用绝对路径就避开了相对路径那些问题。

方法一

将其当做一个module来运行(运行一个module需要在package外面运行)(参考):

1.将当前路径切换到test文件夹的上级目录目录(不是test里面)

2.执行如下命令:

python -m test.m2

注意:首先,多了一个-m选项;其次,路径从test文件夹开始写的,也就是m1和m2的父目录;最后,m2不带.py后缀。此时__package__属性值为test,将通过该test去做相对导入,就没问题。

方法二

如前所述,相对引用依赖模块名,那么不使用相对依赖就没有这个限制,将m2.py文件内容修改如下:

from m1 import a

b = a + 1

if __name__ == '__main__':
    print(b)

相对于之前的版本,只是把m1前面的点号去除了,然后再次运行(目录切换到test里面):

itscs-MacBook-Pro:test itsc$ python m2.py
2

用这个方法的前提是m1这个模块在python的搜索路径中是唯一的。如果不唯一,就需要把test文件夹的路径导入到PYTHONPATH中。

参考

Relative imports in Python 3

https://stackoverflow.com/a/7506029/6381223

PEP 366 -- Main module explicit relative imports

What's the purpose of the “package” attribute in Python?

命令行与环境

Execution of Python code with -m option or not

posted @ 2020-04-06 21:29  ssh_alitheia  阅读(836)  评论(0编辑  收藏  举报