我们在开发项目时,在使用pycharm继承好的环境下,直接点击按钮运行代码,一般不会遇到自己的包找不到的错误。但是当我们在使用命令行运行python代码,在项目的根路径下运行python xxx可能会报错找不到某个模块。
ModuleNotFoundError: No module named 'xxxx'

前言

在介绍这类问题前,不知道你们有没有好奇过。我们平时在python中使用下面的代码调用包时,python是如何找到这些包的? 把这个问题弄清楚,这类问题解决起来就很明白了。

1
2
import os,sys
import xxx

python是通过模块搜索找到这些包的

python模块搜索路径

当一个名为xxx的模块被导入的时候,解释器会在下面的路径里搜索:

  1. 内置模块
  2. 包含输入脚本的目录(或者未指定文件时的当前目录)
  3. PYTHONPATH(一个包含目录列表,它和shell变量有一样的语法)
  4. pip安装的第三方库
  5. ……
    上面所有的搜索路径会初始化在sys.path中
    image.png

与自定义包找不到的关系

了解了python模块搜索的过程,我们再解决自己的包找不到的问题,思路就很清楚了。只要将自己写的包添加在sys.path中就行了。

模块和包的定义

  • 模块:python中每个.py文件称为一个模块
  • 包:每个具有__init__.py文件的目录被称为包

只要自定义的模块或者包所在的目录在sys.path中,就可以使用import导入。(原因在上面已经解释)

几种常见的包导入情况

同级模块的导入

main.pytest.py都在blog_test目录下。在main.py中直接引用test.py后得到的输出如下:
image.png

同级目录下模块的导入

  1. 导入单个模块
    main.pypakage1同级,pakage_a.pypakage1下面的一个模块
    image.png

  2. 所有模块都导入
    main.py想使用from pakage1 import *pakage1中所有模块导入。需要在pakage1下面创建__init__.py文件,并在里面将pakage1中所有包导入。
    image.png

跨级目录的导入

问题描述

跨级目录的导入是最麻烦的
我们在这里想在pakage2/pakage_b.py中导入pakage1/pakage_a.py,但是可以看出得到报错,找不到pakage1这个包。
image.png
在这里将sys.path打印出来,发现第一个路径是pakage2这个包的绝对路径。python在路径搜索的时候就会在pakage2下面搜索。自然是找不到pakage1这个包的。
同样的,这时如果我们在pakage_b.py中导入main.py也是报错

注意: 这个问题如果是使用pycharm中的run直接运行的,则不会出现bug,因为pychram添加了在sys.path中添加了一个路径:D:\JetBrains\PyCharm 2020.1.1\plugins\python\helpers\pycharm_matplotlib_backend。但是实际部署项目我们是通过命令行/终端,这个问题就会出现。

解决方法

我们只需要将pakage1这个包的路径添加到sys.path中就行了,其实我们也可以把项目blog_test的路径添加到sys.path中来解决这个问题,因为会递归查找包。
image.png
可见添加的一行代码是,网上一般用绝对路径,这里写成相对路径,更好些。

1
2
3
4
sys.path.append(os.path.split(sys.path[0])[0])
# 代码解释
# sys.path[0] : 得到C:\Users\maxu\Desktop\blog_test\pakage2
# os.path.split(sys.path[0]) : 得到['C:\Users\maxu\Desktop\blog_test',pakage2']

深入一点

对于上述报错的代码,我们修改一点,在main.py中导入pakage2/pakage_b.py这个模块,然后使用python main.py运行该模块,会发现不报错。
image.png
解释:python是将运行的这个.py文件父文件夹的路径添加到sys.path

结论

导入自己的包出错的问题,一直困扰了我很长时间,现在终于弄明白了。以后再写python项目的时。如果出现包导入错误,可依次进行如下排查:

  • 文件夹下面有没有__init__.py文件来声明该文件夹是个包。
  • 是否是跨目录导入,如果是,是否把路径添加到sys.path中了