1. 问题表现
ImportError: attempted relative import with no known parent package
ModuleNotFoundError: No module named 'xxx'
翻译过来大概的意思是:
- 找不到父包的情况下进行相对导入
- 没有找到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
"""
# base.py
class Base(object):
print("Base")
# a.py
from mypackge.base import Base
class A(Base):
pass
# b.py
from mypackge.base import Base
class B(Base):
pass
# 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中去执行,都会看到有一个项目的绝对路径。
直到现在,我猜你似乎已经发现 ModuleNotFoundError问题具体是什么了。
- 做个测试
# 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的上级目录
# 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
也可以这样写:
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
6. 总结
是否在看某个库的源代码时,发现它们会使用from ..xxx import xxx 或 from .xxx import xxx导入模块,当我们也这样做的时,就出现导入报错。
.module(带点)是相对导入,相对仅在已先导入或已加载父模块时才有效果,这就意味着你需要在当前运行环境中的某处已导入。当使用command执行python3 module.py,它不会事先导入父模块,你需要自行导入,或者from youproject.moduleA import moduleA这样的方式调用。
不管是怎么写,我们要知道:对于python的应用程序,只需使用绝对路径,一切都会好起来