解決python相對匯入出現錯誤:Attempted relative import beyond toplevel package
總結:在使用相對匯入的時候一定要注意包路徑和包的查詢路徑。要在最頂層的目錄新增到 sys.path 中,或者 在最頂層執行指令碼。
相對匯入解決的問題就是消除絕對路徑帶來的硬編碼問題,具體請看文件。
但是在使用相對匯入的時候會出來各種錯誤,其中最讓人費解的可能就是:
Attempted relative import beyond toplevel package
其實這個原因是因為包的層次引起的,跟包的路徑查詢有很大的關係。
先來聊聊python的包的查詢方式:
簡單的來說,Python 有個PYTHONPATH環境變數和安裝路徑,這個環境變數和安裝路徑決定了sys.path(是一個list型別)的值,而python查詢包時,python會查詢當前路徑,然後會按順序查詢sys.path中的路徑。
import sys
print sys.path
通過上面程式碼,你可以看到預設的查詢路徑。
在這裡需要注意的是,python2中,是按上述方式進行的(先查詢當前路徑,再到sys.path),但是在python3中,是先查詢sys.path,找不到再查詢當前路徑的。
具體可以看官方文件(中文):
http://www.pythondoc.com/pythontutorial3/modules.html
如果你看了上面的這邊官方文件,你一定看到了python找列印包路徑的方法:
print __name__
現在讓我們通過這命令來解釋,在使用相對匯入的時候,為什麼會出現:
Attempted relative import beyond toplevel package
先來看一下測試目錄結構:
. ├── run.py └── test ├── dir1 │ ├── __init__.py │ └── moudle1 │ ├── a.py │ └── __init__.py ├── dir2 │ ├── __init__.py │ ├── __init__.pyc │ └── moudle2 │ ├── b.py │ ├── b.pyc │ ├── __init__.py │ └── __init__.pyc ├── run.py └── test.py
a.py中有一個函式:
def test():
pass
現在,要實現的是,在 b. py 中引用 a.py 的內容:
print __name__
from ...dir1.moudle1.a import test
注意到,有兩個 run.py 和 test/run.py 這兩個檔案的程式碼都很簡單,只有一行程式碼:
from test.dir2.moudle2.b import test
然後分別執行上面的run指令碼:
python test/run.py
這個命令將會列印 b 的包路徑並且報錯
包路徑:dir2.moudle2.b
Traceback (most recent call last):
File "test/run.py", line 1, in <module>
from dir2.moudle2.b import test
File "~/Code/python/mytest/test/dir2/moudle2/b.py", line 3, in <module>
from ...dir1.moudle1.a import test
ValueError: Attempted relative import beyond toplevel package
從列印的包路徑來看,這個包的完整路徑只是到dir2就沒了。而我們的相對匯入在解析相對路徑的時候,是根據所在指令碼的包路徑來解析的。
仔細分析一下這個路徑:
dir2 moudle2 b
..(父目錄) .(當前目錄) 包名
從這個表就可以看出來,三級目錄 ... (三個點) 已經超出了最頂層的目錄結構。因此才會報錯。
再看下一個命令:
python run.py
這個命令列印的 b 的包路徑是:
包路徑:test.dir2.moudle2.b
分析下來就是:
test dir2 moudle2 b
...(三級目錄) ..(父目錄) .(當前目錄) 當前目錄
通過上面的比較,可以知道,包路徑跟執行指令碼所在的目錄有關係,而更加本質的原因是,跟包的查詢路徑有關係(python會在執行指令碼所在的路徑查詢包)。對此有疑惑的同學,也可以試試,比如:把不同的包路徑新增到 sys.path 中,然後在其他地方執行run指令碼,是可以得到同樣的結果的。