1. 程式人生 > >解決python相對匯入出現錯誤:Attempted relative import beyond toplevel package

解決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指令碼,是可以得到同樣的結果的。