Python 中的绝对导入和相对导入
  [toc]
当工程大的时候,就需要分出很多包来管理,各个模块间不可避免地出现要引入其它模块。如果没有仔细去了解绝对导入和相对导入的规则,我们在引入模块的时候就很可能遇见各种问题。最常见的就是 ModuleNotFoundError: No module named 'moduleY'、ValueError: attempted relative import beyond top-level package、ModuleNotFoundError: No module named '__main__.moduleY'; '__main__' is not a package等这些异常,本文试着从导入语句的角度带大家了解 Python 的导入机制的冰山一角。
Python 2 和 Python 3 的导入机制会有些差异,本文以 Python 3.8 为基准。
假如我们目前遇到的包的布局如下:
package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py
 2
3
4
5
6
7
8
9
10
# 1 基本原理
在 Python 里面,一个 .py 文件可以称为模块,包含了 __init__.py 文件的称为包。
当一个模块被执行时,Python 会从 sys.path 给出的路径去找在模块中引入的包或其它模块,如果找不到,程序就会报错。
# 2 绝对导入
绝对导入是默认的导入方式,因为它更常见,并且它有相对导入的所有功能。
# 2.1 用法
绝对导入可以使用 import <> 或 from <> import <> 这两种语法,比如在 moduleA 模块里面,使用下面的绝对导入语句是有效的。
import package.subpackage1.moduleX as moduleX
from package.subpackage1 import moduleY
from subpackage2 import moduleZ
 2
3
不过,需要注意的是,在当前模块中引入同级别的模块或者同级别的包下的模块的时候,最好把导入路径写完整。比如,在 moduleX 模块中引入了同一级别的 moduleY:
# moduleX.py
import moduleY
 2
3
moduleA 的代码:
# moduleA.py
import package.subpackage1.moduleX
 2
3
执行 python moduleA.py 会提示 ModuleNotFoundError: No module named 'moduleY'。
因为执行模块 moduleA 的路径是D:/top/package(D:/top 是示例程序在我本机的目录),导入语句import moduleY 将会被解析并定位到 D:/top/package/moduleY.py,很明显这个路径不存在。
把模块 moduleX 中的 import moduleY 改成 import package.subpackage1.moduleY 或 from package.subpackage1 import moduleX 就可以正常运行。
# 2.2 不足
- 导入同一个包下的模块需要写完整的导入路径,如果层级很深,这个路径会显得很长。
 - 假如要改变层级较高的包名,比如顶级包,那么所有导入路径都要改。
 
# 3 相对导入
相对导入解决了绝对导入的一些问题:
- 同一个包下的模块可以很方便的相互引用,使用像 
from . import xxx的语句就行。 - 顶层包的报名改了,包下的模块的相对导入的语句基本不用改。
 
# 3.1 用法
相对导入只能使用 from <> import <> 这种语法,并且使用 . 作为前导点。
比如,在 subpackage1/moduleX.py 或者 subpackage1/__init__.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
 2
3
4
5
6
# 3.2 实现机制
PEP 328 (opens new window) 中有一段介绍了使用相对导入时查找导入模块机制:
Relative imports use a module's
__name__attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to'__main__') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.
简单来说,就是在相对导入里面,一个模块的位置是由该模块的 __name__ 属性来确定,如果该属性值不包含任何包的信息,则把当前模块视为顶层模块。比如,在 moduleA、moduleZ 中分别加入一条可以输出__name__ 、__package__ 的值的语句。
moduleA.py 的代码:
# moduleA.py
print('__file__={0:<35} | __name__={1:<20} | __package__={2:<20}'.format(__file__,__name__,str(__package__)))
from subpackage2 import moduleZ
 2
3
4
moduleZ.py 的代码:
# moduleZ.py
print('__file__={0:<35} | __name__={1:<20} | __package__={2:<20}'.format(__file__,__name__,str(__package__)))
 2
执行命令 python moduleA.py 将会输出:
__file__=D:/top/package/moduleA.py | __name__=__main__             | __package__=None                
__file__=D:\top\package\subpackage2\moduleZ.py | __name__=subpackage2.moduleZ  | __package__=subpackage2         
 2
我们发现 moduleA.py 的 __name__ 是 '__main__',即 moduleA.py 作为顶层模块。moduleZ.py 的 __name__ 是 subpackage2.moduleZ,所以模块 moduleZ.py 的顶级包是 subpackage2 而不是 package 。
# 3.3 限制条件
# 相对导入只适用于顶级包内的模块
在模块里可通过属性 __package__ 获取自身的包信息,即该模块所在包的结构,诸如 XXX、XXX.YYY.ZZZ 等形式,第一个节点就是顶层包,如果 __package__ 为 None 表示该模块是顶层模块。
在模块 moduleY 中引入 subpackage2 包下的 moduleZ 模块:
# moduleY.py
from ..subpackage2 import moduleZ
 2
3
执行文件 moduleA.py 的代码:
# moduleA.py
import subpackage1.moduleY
 2
3
执行 python moduleA.py 会引发 ValueError: attempted relative import beyond top-level package 异常。
因为模块 moduleY 所在的顶级包是 subpackage1,而 subpackage1 包下不存在子包 subpackage2。
# 使用了相对导入的模块文件不能作为顶层执行文件
对于下面moduleX.py 的代码,执行 python moduleX.py 将会引发 ModuleNotFoundError: No module named '__main__.moduleY'; '__main__' is not a package 异常。
# moduleX.py
from .moduleY import *
 2
3