Python 优雅地进行子模块导入


在我们开发 Python 项目时,经常会遇到项目目录下,子目录间模块导入的问题,例如:

我拥有如下目录 python-test:

├─bin
│      start.py
│      __init__.py
│
└─utils
    │  base.py
    │  __init__.py

其中 start.py 是我需要执行的文件,base.py 中定义了某函数方法(例如:hello_world()),并在 start.py 中被调用。

但是这时问题就来了,如果我在此时进入 bin 目录执行 start.py

则会报错:

Traceback (most recent call last):
  File "start.py", line 4, in <module>
    from utils.base import hello_world
ModuleNotFoundError: No module named 'utils'

显示无法找到包 utils

在 Python 中,包可以简单理解为文件系统中的目录,模块就是目录中的文件(包是特殊的模块),一个包含 __init__.py 文件的目录,就会认为是一个 Python 的常规包,但是这块很显然,我们创建了 __init__.py 文件,但是仍然显示无法找到 utils.

有很多方法可以解决上述问题。

方案一:相对导入

对项目做如下修改:

  • 在 python-test 项目目录下,添加 __init__.py 文件
  • 修改 start.py 的导入语句为 from ..utils.base import hello_world
  • 在 python-test 上级目录执行 python -m python-test.bin.start

注:

python -m 
-m mod : run library module as a script (terminates option list)

注意,这其实是将 python-test 作为一个包,将 start.py 作为其中的一个子模块导入的方式。

方案二:添加高一层的目录到 python modules path 下

import sys
sys.path.append("..")

sys.path 是一个字符串列表,用于指定模块的搜索路径,索引的0位,是执行文件所在路径(调用python解释器的脚本的目录),所以我们总是可以调用同目录下的文件,但是对于更高层的,我们就调用不了。

这是一种有效的方法,但是注意,如果使用这种方法,在你执行该文件时,你的执行路径是 ../bin/ 下,而不是 ../python-test/ 下。

而且,实时上直接进入执行目录,然后执行子模块脚本,这并不是一种好的模式,在生产环境中,我们应当尽量避免这种写法。