問題描述
近日在嘗試引用其他文件的代碼時,遇到了錯誤: ImportError: attempted relative import with no known parent package.
問題大致是這樣的:我想在 code2.py
中引用 code1.py
的函數(shù),如 from ..folder1.code1 import xxx
,運行 code2.py
時出現(xiàn)錯誤。
root
├── folder1
│ └── code1.py
├── folder2
│ └── code2.py
└── main.py
太長不看版
如果你要在 code2.py
中引用 code1.py
的函數(shù),那么可以:
改變文件結(jié)構(gòu),考慮在 main.py
中調(diào)用,運行 main.py
code2.py
中增加 root
的位置到搜索路徑 sys.path.append
, 代碼使用 from folder1.code1 import xxx
用 -m
選項運行: python -m root.folder2.code2
,代碼可以使用 from folder1.code1 import xxx
或 from ..folder1.code1 import xxx
[我認(rèn)為這是最優(yōu)解!]
詳細(xì)解釋
如果對導(dǎo)入的概念不是很理解的話,可能會遇到:
ModuleNotFoundError: No module named 'xxx'
ImportError: attempted relative import with no known parent package
首先明確兩種導(dǎo)入方法:
from xxx import yyy
則是從已知的模塊導(dǎo)入- “relative import” 即
from .xxx import yyy
,根據(jù)從當(dāng)前文件的相對路徑導(dǎo)入。
第一種方法
具體可參考官方文檔 the-module-search-path
僅適用于模塊(文件夾)或腳本(文件)存在于搜索路徑中,導(dǎo)入時,Python 解釋器會首先搜索內(nèi)置模塊,如果沒有,則去以下三個位置搜索:
- 當(dāng)前文件所在目錄
- 環(huán)境變量
PYTHONPATH
指定的目錄 - Python 默認(rèn)的安裝目錄
可以查看 sys.path
,顯然,當(dāng)前運行腳本所在的文件夾被放在了搜索路徑的首位,因此該文件夾下的所有內(nèi)容均可被引入。
import sys
print(sys.path)
# ['/.../path-to-this-folder', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/thor/.local/lib/python3.10/site-packages', '/usr/local/lib/python3.10/dist-packages', '/usr/lib/python3/dist-packages']
要解決開頭提出的問題,即引入其他文件夾下的內(nèi)容,可以把 root 的位置添加到搜索路徑中:(好吧,這樣很不優(yōu)雅……)
import sys
sys.path.join("/path/to/root") # 用絕對路徑,需要從根目錄開始
sys.path.join("..") # 用相對路徑,但是命令行當(dāng)前位置不能出錯
from folder1.code1 import xxx
可以參考這段代碼:
if __package__:
from .. import config
else:
sys.path.append(os.dirname(__file__) + '/..')
import config
第二種方法
具體可參考官方文檔 packages
需要明確的是,這種方法只適用于 package 內(nèi)部!
當(dāng)你把 code2.py
作為腳本運行時,即 python code2.py
,此時 python 并不會認(rèn)為它屬于某一個 package, 即使存在 __init__.py
??梢?nbsp;print(__package__)
進(jìn)行驗證,作為腳本運行時為 None
,否則則應(yīng)該為 xxx.yyy
的形式。
(網(wǎng)絡(luò)上有很多地方都說添加 __init__.py
就可以解決問題,但事實是并不會 ,在我的測試中,在本文提到的所有的解決方法中,添加 __init__.py
與否似乎不會帶來什么影響。)
因此,開頭描述的問題中,要使用相對導(dǎo)入的形式在 code2.py
中引用 code1.py
的代碼,必須使用:
python -m root.folder1.code1
這里把 root 及其內(nèi)部當(dāng)作一個完整的 package,而 package 內(nèi)的腳本可以使用相對導(dǎo)入互相引用。
?這里不帶 .py
后綴。
?不可以為 python -m folder1.code1
,此時把 folder1 及其內(nèi)部當(dāng)作一個完整的 package, 無法引用到以外的內(nèi)容,會遇到 ImportError: attempted relative import beyond top-level package
除了命令行調(diào)用時進(jìn)行調(diào)整,在腳本中 import 也是一樣的道理:
newroot
├── root
│?? ├── folder1
│?? │?? └── code1.py
│?? ├── folder2
│?? │?? └── code2.py
│?? └── main.py
└── upper_main.py
在 upper_main.py
中添加 from root.folder2 import code2
并運行時,它會把 root
當(dāng)作一個包,此時code2.py
中的 from ..folder1.code1 import xxx
可以正常執(zhí)行
在 main.py
中添加 import folder2.code2
并運行時,它會把 folder2
當(dāng)作一個包,此時 code2.py
中的 from .xx import
可以正常執(zhí)行,而 from ..folder1.code1 import xxx
會遇到 ImportError: attempted relative import beyond top-level package.
其他
說明:
- 這里僅說明我嘗試成功得出的經(jīng)驗,不排除有其他正確做法。
- 我還看到過類似
code2.py
中有from folder1 import code1
這種做法,沒有測試過其適用條件,不過模塊內(nèi)部感覺使用相對引用比較好。