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