ImportError: No module named *** 问题?——理解绝对导入和相对导入

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Levy_96/article/details/86629444

前言

Python 开发者一定对ImportError: No module named ***这个报错不陌生,特别是对于初学者来说,代码在本地 PyCharm 中运行得好好的,一放到服务器上用命令行启动就报这个错,这是很常见的情形。
那么这个问题到底是什么引起的,又该怎么解决?这就涉及到 Python 的绝对导入和相对导入,这里推荐阅读 PEP328

解决 ImportError: No module named ***

什么是绝对导入?什么是相对导入?先举几个简单的例子:

from B import C  # 绝对导入

from . import D  # 相对导入
from ..E import F  # 相对导入

Python 编译器对于绝对导入的处理是从当前目录、sys.path、环境变量 PYTHONPATH 中搜索需要导入的包。回到ImportError: No module named ***的问题,如果你用绝对导入引起了这个问题,那就要思考从当前目录、sys.path、环境变量 PYTHONPATH 中能不能找到需要导入的包?

知道了起因,就很好解决问题了,如下所示:

import sys
sys.path.append('你的项目目录')
from B import C

需要在代码里直接写路径,感觉有点丑陋?稍作修改:

import 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 中运行代码就没有这样的问题呢?

import sys
import os


if __name__ == '__main__':
    print(sys.path)
    print(os.environ['PYTHONPATH'])

分别用 PyCharm 和命令行运行这段代码就能发现:

  1. 不管是用 PyCharm 运行还是命令行运行,sys.path 中都包含当前文件的所在路径以及一系列 Python 相关的路径(包括 site-packages ),这就解释了为什么可以用import A的方式导入当前目录的模块、标准库的模块、已安装的第三方模块;
  2. 用PyCharm 运行,sys.path 中还多了你的项目根目录,也就是上述用sys.path.append添加的路径,这就解释了为什么 PyCharm 运行代码没有ImportError: No module named ***的错误,这是 PyCharm 默默做了处理;
  3. 命令行运行print(os.environ['PYTHONPATH'])会报错,而 PyCharm 不会,并且 PyCharm 会把项目目录添加到环境变量中去,这和上边一条是同样道理。

现在,ImportError: No module named ***的问题解决了,但是关于绝对导入和相对导入还不止这些。

如何使用相对导入

使用 Guido 的例子:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

如果要在moduleX.py中相对导入其他模块,要这么写:

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

absolute_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种方式:

import 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 的目录)。但是相对导入也是被允许的,出于以下考虑:

  1. 项目目录较深时,绝对导入会产生冗长的导入语句;
  2. 对顶层包名修改时,就要在每个绝对导入语句中相应修改包名,这是件令人头疼的事。

而绝对导入的优势在于能明确表达模块之间的结构关系,而不像相对导入要去理清一个个.的关系。

所以在目录结构不复杂的项目中使用绝对导入是可行的,而相对导入也是可以酌情考虑使用。

展开阅读全文

没有更多推荐了,返回首页