解决导包报错:ModuleNotFoundError,ImportError

1. 问题表现

ImportError: attempted relative import with no known parent package
ModuleNotFoundError: No module named 'xxx'

翻译过来大概的意思是:

  1. 找不到父包的情况下进行相对导入
  2. 没有找到xxx模块

2. 尝试去了解

既然找不到父包,那么该如何才能找到它呢?

正如出现上述问题,我个人习惯性直接寻找解决方案,但其实是没深入发掘过,下一次再次遇到了,可能还是知其然不知所以然。

打个比如:某天我正在用浏览器兴致勃勃看着某站视频时,画面突然卡住不动,最先容易想到是不是我的网络断了对吧?

那么这时候,稍微有点网络知识,可能就会去ping一下看看网络状况,进而做下一步诊断。

同样的,在出现上述报错的问题时,使用一个常规方法,即通过sys.path输出的列表,观察Python的环境目录,从而找出错误根源。

3. 先说结论

先说结论:

对于python的应用程序,只需使用绝对路径(不带点哦,如 from .xxx import xxx)一切都会好起来的。

4. 具体问题分析

  • 项目结构和具体程序
"""
├─app
|  ├─mypackge
|    ├─module
|      ├─init.py
|      ├─a.py
│      ├─b.py
|      ├─c.py
|    ├─init.py
|    ├─base.py
"""

# mypackge/base.py
class Base(object):
    print("Base")


# mypackge/module/a.py
from mypackge.base import Base
class A(Base):
    pass


# mypackge/module/b.py
from mypackge.base import Base
class B(Base):
    pass


# mypackge/module/c.py
from mypackge.base import Base
class C(Base):
    pass

在pycharm中打开上述项目,当我们运行a.py时,一切都很正常,但当我使用command时,python3 a.py,会出现什么问题呢?

出现: ModuleNotFoundError: No module named 'mypackge'

好了,回到最先开始提到的,尝试使用sys.path去发现具体问题,把它放在代码最前面:

import sys
print(sys.path)

# 你的代码

# 当使用pycharm解释器输出:
['C:\\Users\\Rosesx\\Desktop\\app\\mypackge\\module', 'C:\\Users\\Rosesx\\Desktop\\app', 'D:\\Python\\Python39\\python39.zip', 'D:\\Python\\Python39\\DLLs', 'D:\\Python\\Python39\\lib', 'D:\\Python\\Python39', 'D:\\Python\\Python39\\lib\\site-packages', 'D:\\Python\\Python39\\lib\\site-packages\\win32', 'D:\\Python\\Python39\\lib\\site-packages\\win32\\lib', 'D:\\Python\\Python39\\lib\\site-packages\\Pythonwin']

# 当使用command时,python3 a.py,输出:
['C:\\Users\\Rosesx\\Desktop\\app\\mypackge\\module', 'D:\\Python\\Python39\\python39.zip', 'D:\\Python\\Python39\\DLLs', 'D:\\Python\\Python39\\lib', 'D:\\Python\\Python39', 'D:\\Python\\Python39\\lib\\site-packages', 'D:\\Python\\Python39\\lib\\site-packages\\win32', 'D:\\Python\\Python39\\lib\\site-packages\\win32\\lib', 'D:\\Python\\Python39\\lib\\site-packages\\Pythonwin']
Traceback (most recent call last):
  File "C:\Users\Rosesx\Desktop\app\mypackge\module\a.py", line 15, in <module>
    from mypackge.base import Base
ModuleNotFoundError: No module named 'mypackge'

注意观察是不是发现什么?pycharm和command解释器分别输出的内容中,pycharm打印的环境目录列表多了一个项目app绝对路径

是的,经过我多次测试,无论我把sys.path放到项目中那个.py中去执行,使用pycharm运行都会看到project的绝对路径。

直到现在,我猜你似乎已经发现 ModuleNotFoundError问题具体是什么了。

  • 做个测试
# module/a.py
import sys
from pathlib import Path

for path in sys.path:
    A = Path(path, r'mypackge\base.py') # 遍历python环境目录然后拼接mypackge\base.py
    B = r'C:\Users\Rosesx\Desktop\app\mypackge\base.py' # 预期输入
    if str(A) == B: # 测试一下实际输出结果A和预期输入B
        print(True) 
    else:
        print(False)

from mypackge.base import Base

class A(Base):
    pass

# 当使用pycharm解释器输出:
False
True
False
False
False
False
False
False
False
False
Base

# 当使用command时,python3 a.py,输出:
False
False
False
False
False
False
False
False
False
Traceback (most recent call last):
  File "C:\Users\Rosesx\Desktop\app\mypackge\module\a.py", line 15, in <module>
    from mypackge.base import Base
ModuleNotFoundError: No module named 'mypackge'

经过这么一个小测试,发现使用pycharm解释器输出的实际结果有一条返回True,那么是不是可以说明from mypackge.base import Base成功搜索到它的上级,也就是匹配上了绝对路径。而command输出并没有找到自己的上级而报错。

5. 解决思路

顺着上面得到的验证,当使用command执行python3 a.py时,我们发现它没有导入父模块(也就是说没有找到项目绝对路径)。

这就好办了,在不改动from mypackge.base import Base前提下,加入mypackge的上级目录

# mypackge/module/a.py
import sys
from pathlib import Path

sys.path.append(r'C:\Users\Rosesx\Desktop\app') # 加入mypackge的父模块

from mypackge.base import Base

class A(Base):
    pass

也可以这样写:

# mypackge/module/a.py
import sys
from pathlib import Path

path = Path(__file__).parent.parent.parent  # 链式调用,返回当前a.py的上上上目录
print(path)  # 输出: mypackge上级路径

sys.path.append(str(path))

from mypackge.base import Base


class A(Base):
    pass

看到这里,如果你觉得这样写有点乱套,都是写啥ex,难道就没有更好的写法了吗?

我们接着看下面的:

# module/context.py

import os.path
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from base import Base

我在module目录下新建一个名叫context.py的文件,它会去创建一个上下文环境,以便满足我们无需再为a.py,b.py,c.py每一个模块设置一个独立的环境,不然也太麻烦了。

在module/context.py中,我们使用os.path.join()方法去拼接_file_(context.py)的目录路径和..,os.path.abspath()方法可以返回当前脚本的完整路径,我们知道..是返回上一层路径,那么这里输出结果就是module的上一层路径,即mypackage路径,而base就在mypackage目录下,所以这里写成from base import Base。(这样写pycharm上面可能会出现红色的波澜线,最终以执行结果为准哦)

我们再回顾一遍现在的项目结构:

"""
├─app
|  ├─mypackge
|    ├─module
|      ├─init.py
|      ├─a.py
│      ├─b.py
|      ├─c.py
|      ├─context.py  #新增,独立管理额外的依赖和运行环境
|    ├─init.py
|    ├─base.py
"""

现在我们的每一个模块可以改写成如下这样

# mypackge/module/a.py

from context import Base 

class A(Base):
    pass

# pycharm和command解释器输出均正常

6. 总结

是否在看某个库的源代码时,发现它们会使用from ..xxx import xxx 或 from .xxx import xxx导入模块,当我们也这样做的时,就出现导入报错。

.module(带点)是相对导入,相对仅在已先导入或已加载父模块时才有效果,这就意味着你需要在当前运行环境中的某处已导入。当使用command执行python3 module.py,它不会事先导入父模块。你需要自行导入,使用from youproject.moduleA import moduleA这样的方式调用,或者把依赖的运行环境加入到一个单独的模块。

最后,相信你已有所收获,欢迎下方留言交流。
直到现在,再梳理梳理,当需要导入包时,我们有几种方式处理:

  • 将导入的包安装/移动到python目录,比如说site-packages中。
  • 通过简单的直接将路径设置来解决导入的问题。

不管是怎么写,我们要知道:对于python的应用程序,只需使用绝对路径,一切都会好起来

更多资料:https://docs.python.org/zh-cn/3/tutorial/modules.html

posted @ 2021-07-17 17:17  Rosaany  阅读(4895)  评论(2编辑  收藏  举报