背景
现象
遇到一个坑,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
中。
参考
https://stackoverflow.com/a/7506029/6381223
PEP 366 -- Main module explicit relative imports