解決導包報錯:ModuleNotFoundError,ImportError
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的應用程式,只需使用絕對路徑,一切都會好起來