Python 的 import 机制是最令用户困惑的地方之一,在实践中非常容易出错,相信被 ImportError 和 ModuleNotFoundError 折磨过的同学都对此深有体会。本文完整地梳理 Python 的各种导入逻辑,力求在实践中避坑并提出一些最佳实践。
PS:本文中的导入语句均以如下所示的目录结构为例进行演示:
12345678910package/ __init__.py subpackage1/ __init__.py moduleX.py moduleY.py subpackage2/ __init__.py moduleZ.py moduleA.py
Python 的导入行为可以分为绝对导入与相对导入两类:
绝对导入
绝对导入即指定 package 或 module 的绝对名称或路径,经常用于导入内置库或第三方库,例如:
12import os # 导入内置库import requests # 导入第三方库
事实上,绝对导入是通过依次搜索 sys.path 列表中的所有路径来完成的,这一点类似于操作系统的 PATH 环境变量。一个目录只要加入到了 sys.path 中,那么其中直接包含的任意 package 或 module 均可实行绝对导入。例如:
123456789import syspath = '/path/to/package'if path not in sys.path: sys.path.insert(0, path)import moduleAimport subpackage1.moduleXfrom subpackage1 import moduleYimport subpackage2
除了在代码运行时动态添加 sys.path 外,还有一个环境变量可以在 Python 进程启动时设定 sys.path 的初始值,即 PYTHONPATH。一些需要经常引用的本地目录可以加入 PYTHONPATH 中,这样就不用每次都在代码中修改 sys.path 了。
此外,通过 Python 命令启动脚本或模块时会把父进程(通常是命令行)的当前目录加入 sys.path 中,因此当前目录下的任意 package 或 module 也可以直接进行绝对导入。例如在 shell 中执行如下命令调用 Python 脚本:
12cd /path/to/packagepython moduleA.py # or python -m moduleA
那么在 moduleA.py 中可以进行以下绝对导入:
123import subpackage1.moduleXfrom subpackage1 import moduleYimport subpackage2
相对导入
相对导入是指同一个顶层 package 内部不同 module 之间的导入行为,这是大前提。很多文章包括官方文档在讲解相对导入时往往没有强调这个前提,导致大量的误解。
相对导入包含一个或多个前导的 .,其格式为 from .xxx import yyy,例如:
12from . import moduleAfrom .subpackage1 import moduleX
其中,. 代表当前 package,.. 代表上层 package,... 代表上上层 package,以此类推。
此外,还有一种称为“隐式相对导入”的方式,其导入语句格式与绝对导入完全一样。例如在 moduleX 中引用 moduleY:
12import moduleY # 隐式相对导入from . import moduleY # 显式相对导入
隐式相对导入容易与绝对导入混淆,非常不推荐,已被 Python3 废弃。如果希望在使用 Python2 时也废弃这种语法,可以在代码中加上以下语句:
1from __future__ import absolute_import
后文中所提到的相对导入如无特殊说明均指显式相对导入。
在相对导入中,当目录结构与导入语句均确定时,能否断定一个绝对导入/相对导入一定正确或错误的?
答案是不能,需要视情况而定。根据程序调起的方式不同,同样的 import 语句有时候是正确的,有时候会报错。这就是相对导入最让人困惑的地方。
对此,我们需要了解 Python 程序的不同调起方式。
Python 程序的三种调起方式
作为脚本直接运行:python package/subpackage1/moduleX.py
作为模块直接运行:python -m package.subpackage1.moduleX
从别的模块中导入:import package.subpackage1.moduleX
Python 的导入机制依赖 sys.path、__package__ 和 __name__ 三个变量。以上三种调用方式会对这三个变量产生不同的作用。当执行到 moduleX.py 内部时,有:
方式 1:__package__ 为 None; __name__ 为 '__main__'; 当前目录和脚本所在目录被加入 sys.path。
方式 2:__package__ 为 'package'; __name__ 为 '__main__'; 当前目录被加入 sys.path。
方式 3:__package__ 为 'package'; __name__ 为 'moduleA'。sys.path 中具体加入了什么路径,要看程序入口是怎么调起的。
所谓相对导入,相对的就是 __package__ 所代表的包名。当执行 from .moduleY import func 时,实际上相当于解析 from __package__.moduleY import func。如果 __package__ 为 None,则会解析 from __name__.moduleY import func(后文讨论 __package__ 的取值时,均已包含该降级逻辑)。
相对导入可能抛出的错误包括以下几种:
如果 __package__ 为 ''(一般出现在类似 python -m moduleX 这样的调用方式中),会抛出 ImportError: attempted relative import with no known parent package 错误。
如果 __package__ 不为空,但在 sys.path 的所有路径中均未搜索到 __package__ 所代表的包名,会抛出类似 ModuleNotFoundError: No module named 'xxxpackage.moduleY'; 'xxxpackage' is not a package 的错误。
如果 __package__ 不为空且存在对应的包,但其中没有 moduleY 模块,会抛出类似 ModuleNotFoundError: No module named 'xxxpackage.moduleY' 的错误。
如果试图从上级 package 中进行相对导入,例如 from ..moduleA import func,那么必须确保 __package__ 是多级 package,例如 __package__ = 'package.subpackage1'。如果 package 级别数小于上溯的级别数,例如 __package__ = 'subpackage1',将会抛出 ValueError: attempted relative import beyond top-level package 错误。
由此可见,相对导入必须确保 __package__ 有合适的取值,也就是只能用于上述第 2、3 种调起方式。尽管第 1 种调起方式是最常用的,但不幸的是在这种方式下只能使用绝对导入,不能使用相对导入。
绝对导入与相对导入对比
绝对导入由于其含义非常明确,且在任何调起方式中均可以使用,因而被 PEP8 所推荐。
绝对导入唯一的缺点是将 package 名称硬编码到了代码中,会带来维护问题。例如修改了某一顶层包名之后,那么其内部的所有绝对导入代码都需要相应修改。
而相对导入就可以避免这种维护问题,当包名修改之后内部代码无需做任何改动。但相对导入的解析机制更加复杂,容易因为使用不当而报错。并且使用了相对导入的 py 文件无法再作为脚本直接运行。
最佳实践
结合绝对导入与相对导入二者的优缺点,推荐一种关于绝对导入与相对导入的最佳实践:
一般情况下使用绝对导入。
如果要构建一个 package 供外部调用,例如给其他脚本调用或发布到 PYPI,则在该 package 内部使用相对导入。
对于使用了相对导入的脚本,如果想直接运行其中的 if __name__ == '__main__': 代码块(通常用于简单测试当前 module 的功能),可以使用 python -m package.module 的方式调起,避免使用 python package/module.py。