前言
Python 开发者一定对ImportError: No module named ***
这个报错不陌生,特别是对于初学者来说,代码在本地 PyCharm 中运行得好好的,一放到服务器上用命令行启动就报这个错,这是很常见的情形。
那么这个问题到底是什么引起的,又该怎么解决?这就涉及到 Python 的绝对导入和相对导入。
解决 ImportError: No module named ***
什么是绝对导入?什么是相对导入?先举几个简单的例子:1
2
3
4from B import C # 绝对导入
from . import D # 相对导入
from ..E import F # 相对导入
Python 解释器对于绝对导入的处理是从当前目录、sys.path、环境变量 PYTHONPATH 中搜索需要导入的包。回到ImportError: No module named ***
的问题,如果你用绝对导入引起了这个问题,那就要思考从当前目录、sys.path、环境变量 PYTHONPATH 中能不能找到需要导入的包?
知道了起因,就很好解决问题了,如下所示:1
2
3import sys
sys.path.append('你的项目目录')
from B import C
需要在代码里直接写路径,感觉有点丑陋?稍作修改:1
2
3
4
5
6
7import sys
import os
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
# os.path.realpath(__file__)表示当前文件的路径,加上os.path.dirname就是当前文件的上一级目录路径
# 套多少个dirname()取决于当前文件在你的项目目录中的深度有多少。
# 注意不要把realpath写成relpath!
from B import A
但是,为什么在 PyCharm 中运行代码就没有这样的问题呢?1
2
3
4
5
6
7import sys
import os
if __name__ == '__main__':
print(sys.path)
print(os.environ['PYTHONPATH'])
分别用 PyCharm 和命令行运行这段代码就能发现:
- 不管是用 PyCharm 运行还是命令行运行,sys.path 中都包含当前文件的所在路径以及一系列 Python 相关的路径(包括 site-packages ),这就解释了为什么可以用
import A
的方式导入当前目录的模块、标准库的模块、已安装的第三方模块; - 用PyCharm 运行,sys.path 中还多了你的项目根目录,也就是上述用
sys.path.append
添加的路径,这就解释了为什么 PyCharm 运行代码没有ImportError: No module named ***
的错误,这是 PyCharm 默默做了处理; - 命令行运行
print(os.environ['PYTHONPATH'])
会报错,而 PyCharm 不会,并且 PyCharm 会把项目目录添加到环境变量中去,这和上边一条是同样道理。
现在,ImportError: No module named ***
的问题解决了,但是关于绝对导入和相对导入还不止这些。
如何使用相对导入
使用 Guido 的例子:1
2
3
4
5
6
7
8
9
10package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
如果要在moduleX.py
中相对导入其他模块,要这么写:1
2
3
4
5
6from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo
一个.
表示当前目录,两个.
表示上级目录,以此类推。
需要注意的是使用相对导入后,当前模块就不能直接运行了,会抛出ValueError: Attempted relative import in non-package
的错误。这是因为对于编译器来说,它无法理解导入语句中的相对关系,这时候就需要为它说明相对关系了,也就是用python -m A.B.C
的方式代替python A/B/C.py
来运行模块。
from future import absolute_import
在网上很多文章提到 “ Python2 默认为相对导入,而 Python3 默认为绝对导入 ”。
我个人觉得这句话是挺没头没尾的,乍一看就会有疑问:相对导入还是绝对导入难道不是由导入语法决定的么?难道在 Python3 中就不能用相对导入?
我的理解是 “ 采用优先使用绝对导入的逻辑,并且这在 Python3 中是强制的”,这要提到from __future__ import absolute_import
,查看absolute_import
:1
2
3absolute_import = _Feature((2, 5, 0, "alpha", 1),
(3, 0, 0, "alpha", 0),
CO_FUTURE_ABSOLUTE_IMPORT)
可见absolute_import
这个模块在 2.5 版本后可以选择性引入,在 3.0 之后是默认使用的,那么引入这个模块有什么用呢?
以上边的项目目录结构为例,在moduleX.py
中导入moduleY.py
可以有3种方式:1
2
3import moduleY # 隐式相对导入
from . import moduleY # 显式相对导入
from package.subpackage1 import moduleY # 绝对导入
在使用from __future__ import absolute_import
后,就不支持import moduleY
的写法了,也意味着在 Python3 中无法用这种写法,这是为了避免本地自定义的模块与标准库的模块命名出现冲突,如果当前目录和标准库同时存在一个名为 A 的模块,那么import A
将从标准库中导入模块 A,忽略当前目录的 A 模块。这样,import ***
就只能用于标准库或已安装的第三方模块的导入。
应该用绝对导入还是相对导入?
按照 PEP8 的标准,建议使用绝对导入,例如from A.B import C
(从顶级包开始,A 就是这里的顶级包,也就是最外层的含有 __init__.py
的目录)。但是相对导入也是被允许的,出于以下考虑:
- 项目目录较深时,绝对导入会产生冗长的导入语句;
- 对顶层包名修改时,就要在每个绝对导入语句中相应修改包名,这是件令人头疼的事。
而绝对导入的优势在于能明确表达模块之间的结构关系,而不像相对导入要去理清一个个.
的关系。
所以在目录结构不复杂的项目中使用绝对导入是可行的,而相对导入也是可以酌情考虑使用。