Python 中从其他模块或库中导入方法或模块的方法是利用 import 关键字,但是,当工程比较复杂时,比如分成多个类别的库;当代码需要在 jupyter 中调用工程中的模块时,工程中各模块的互相引用就显得非常重要,如果导入模块的姿势不对,将导致程序无法运行。本篇总结常用的导入方法,对 import 关键字进行介绍。

import 常规的导入方法这里不再重复,直接介绍复杂工程中或多种运行方式(命令行、ipython、jupyter 等)下模块的导入。

工程结构

我这里创建了一个测试工程,目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
$ tree mytest
mytest
├── p1
│   ├── __init__.py
│   └── q1.py
└── p2
├── __init__.py
├── q2.py
├── q3.py
└── q4.py

2 directories, 6 files

其中,mytest 是工程名字,p1 和 p2 分别是两个库,里面分别有模块 q1,q2 和 q3,这里给出他们的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ cat mytest/p1/q1.py
def add(x, y):
return x + y


$ cat mytest/p2/__init__.py
import os
PROJECT_PATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))


$ cat mytest/p2/q2.py
import sys
from . import PROJECT_PATH
sys.path.insert(0, PROJECT_PATH)
from p1 import q1
z = q1.add(2, 3)
print(z)


$ cat mytest/p2/q3.py
import sys
print(sys.path)
from p1 import q1
z = q1.add(2, 3)
print(z)


$ cat mytest/p2/q4.py
from .. import p1
z = p1.q1.add(2, 3)
print(z)

绝对导入

请查看 mytest/p1/q1.pymytest/p2/q3.py,在 q3 中导入了上一层目录 p1 里面的 q1 模块,使用该模块的 add 方法。属于绝对导入,这种方法以工程名所在目录为家目录,使用时必须保证 mytest 在 python 运行时的环境变量中。

请查看 mytest/p1/q1.pymytest/p2/q2.py,在 q2 中导入了上一层目录 p1 里面的 q1 模块,使用该模块的 add 方法。在使用绝对导入前,增加了相对导入 from . import PROJECT_PATH,使用动态的方式,将环境变量 mytest 在每次调用模块 q2 是添加到 python 环境变量中。

查看环境变量的方法:

1
2
import sys
sys.path

命令行运行

此时,想要运行 q3.py,可使用如下方法:

1
2
3
cd mytest
python -m p2.q3
5

该方法的更多信息请查看另一篇文章:在命令行运行 python 程序的方法

注意,此时使用如下方法,将会报错:

1
python p2/q3.py

因为此方法以 q3.py 所在目录为主目录:'/home/jinzhongxu/PythonProjects/mytest/p2',运行时将该目录添加到 sys.path 中,但这与 q3.py 中的模块导入 from p1 import q1 不符,因为 p1 不在被访问到,它不在 p2 目录下。但,python -m p2.q3 会将 '/home/jinzhongxu/PythonProjects/mytest' 作为主目录,运行时将该目录添加到 sys.path 中,因此,在 q3.py 中能够使用 模块导入 from p1 import q1.

运行 q2.py

1
2
3
cd mytest
$ python -m p2.q2
5

但如下方法将会报错:

1
2
3
4
5
6
cd mytest
$ python p2/q2.py
Traceback (most recent call last):
File "/home/jinzhongxu/PythonProjects/mytest/p2/q2.py", line 2, in <module>
from . import PROJECT_PATH
ImportError: attempted relative import with no known parent package

ipython 调用

如果运行方法如下,则同样能够通过:

1
2
3
4
5
6
7
8
9
10
11
12
cd mytest
$ ipython
Python 3.9.5 (default, Jun 4 2021, 12:28:51)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.30.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from p2 import q2
5

In [2]: from p2 import q3
['/home/jinzhongxu/PythonProjects/mytest', ...]
5

可以看到,该方法同样会将 '/home/jinzhongxu/PythonProjects/mytest' 添加到 sys.path 中。

jupyter 调用

如果想在 jupyter 中随处都调用 p2/q2.pyp2/q3.py,一种可行的方法是将 '/home/jinzhongxu/PythonProjects/mytest' 添加到环境变量中,此方法可参考:在 Python 中导入自己编写的模块,此时,在 jupyter 中调用方法如下:

1
2
from p2 import q2
from p2 import q3

如果添加的是 '/home/jinzhongxu/PythonProjects' 到 module.pth 中,那么 from p2 import q3 方法将失效,因为 mytest 不在 sys.path 中,但 from p2 import q2 确定正常执行,因为,模块 q2.py 会在程序运行时动态导入 mytestsys.path 中。

相对导入

请查看 mytest/p1/q1.pymytest/p2/q4.py,在 q4 中导入了上一层目录 p1 里面的 q1 模块,使用该模块的 add 方法。属于相对导入。

命令行运行

1
2
cd mytest
python -m p2.q4

1
2
cd mytest
python p2/q4.py

都将报错

ipython 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ipython
Python 3.9.5 (default, Jun 4 2021, 12:28:51)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.30.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from p2 import q4
---------------------------------------------------------------------------
ImportError Traceback (most recent call last)
<ipython-input-1-848272f17660> in <module>
----> 1 from p2 import q4

~/PythonProjects/mytest/p2/q4.py in <module>
1
----> 2 from .. import p1
3
4 z = p1.q1.add(2, 3)
5 print(z)

jupyter 调用

同样会报错。

结论

当代码只在命令行中运行时,可简单的采用 q3.py 的模式。运行方法采用 python -m q3.py.

当代码不仅需要在命令行中运行,还需要再 jupyter 中调用时,配置环境变量很重要,推荐采用 q2.py 的模式。

尽量不要使用相对导入。