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