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
"""
# 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的應用程序,只需使用絕對路徑,一切都會好起來