本文假设读者已理解sys_path
变量和__name__
属性,如有不清楚的地方,可以参考旧文:
sys_path
变量: python中sys模块必备知识
__name__
属性:python中必备模块属性详解
python提供了两种导入模块和包的方法,分别是绝对导入和相对导入。绝对导入是指以sys.path
中的路径作为基本路径,从这里开始搜索模块并导入,相对路径是指从当前模块出发,根据其他模块的相对路径关系导入。
绝对导入
我们平时导入python中自带的库(如os
、math
等)使用的都是绝对导入。与相对导入比起来,绝对导入的适用范围更广。比如说,在python主程序(__name__
为__main__
的模块)中不能使用相对导入,只能使用绝对导入,否则将会报错ValueError: Attempted relative import in non-package
。
导入方法:
1 | import <something> |
相对导入
相对导入使用前缀点号,与linux的相对命令一样,
一个.
表示相对导入从当前目录开始,
两个或更多.
表示对当前目录的上级目录的相对导入,第一个点号之后的每个点号代表上一级。
导入方法是:
1 | from <where> import <somethings> |
相对于绝对导入,相对导入不能使用import <something>
的形式,因为import <something>
要求something
是python表达式,而如.module
并不符合这一要求,直接运行的话解释器会将其当作错误语法处理。
导入方式
在比较大的项目代码中,进行导入建议分两步处理:
- 确保在运行时项目的根目录存在于
sys.path
中; - 在项目的不同模块包之间使用绝对导入,而在同一个包内部可以更方便的使用相对导入(当然,主程序除外)。
举个例子,存在如下项目:
1 | # /home/ven/project |
情形1
首先从一个简单的情况开始,我们将file4.py
作为主程序。
file4.py
直接处于项目的根目录下,旧文 python中sys模块必备知识提到过,在运行时解释器会自动把主程序所在的目录加入sys.path
中,在这里恰好将项目的根目录/home/ven/project
加了进去。若要导入其他项目模块,直接使用绝对导入即可(主程序不能使用相对路径,还记得吧?)。比如若要调用file1.py
中的func1
、file2.py
中的func2
。那么导入方式如下:
1 | # file4.py |
同时如果file2.py
中需要调用file3.py
中的func3
的话,如何导入呢?这时候绝对导入和相对导入都可以,通常相对导入更简便和规范:
1 | # file2.py |
更进一步的,如果file2.py
中需要调用file1.py
中的func1_1
的话,相对导入和绝对导入也都可以,这时绝对导入更为推荐:
1 | # file2.py |
情形2
现在考虑一个复杂一点的情况,如果我们的主程序不是file4.py
而是file2.py
怎么办呢?
开始运行时解释器会自动将file2.py
所在目录/home/ven/project/folder2
加入sys.path
中,而这个目录并不是根目录。这时候就需要手动将根目录加到sys.path
中,旧文
python中sys模块必备知识提供了一个简单粗暴的方法:
1 | # file2.py |
这当然是正确的,只是不够严谨。假如说这个项目后来被移动到了另一路径/home/ven/big_project/project
下,那么项目将无法正常运行,因为仍然会去/home/ven/project
下找模块。
那么有没有更好的添加根目录的方法呢?当然有,这里提供两种:
1 | # file2.py |
第一种方法借助__file__
属性(参考旧文:python中必备模块属性详解)将根目录的绝对路径加进sys.path
中,第二种则是通过在sys.path
加入相对路径使得解释器能够找到根目录(注意这里的相对路径和绝对路径与相对导入和绝对导入是不同的概念)。
掌握python的导入规则对于写大型项目来说是一项基础而重要的技能,否则项目运行中将难免出现各种奇奇怪怪的导入报错,占用大量的时间和精力。本文对python导入规则做了一遍记录和梳理,希望能有助于读者理清相关的知识。
另外,为了方便起见,本文中举的例子都没有建立__init__.py
(这在实际项目中可不是好的习惯),所以仅使用“目录”而非“包”的叫法。