python相對包導入報“Attempted relative import in non-package”錯誤
在python當中使用相對包導入有的時候是一件非常讓人痛苦的事情,有的時候使用了相對包導入明明可以在運行,但是換了一種運行方式又不可以了。這篇文章就要深度的解決這個問題,在看的過程要不斷的敲代碼來練習,領會python的相對包導入。
這篇文章是從stackoverflow翻譯過來的,
Relative imports for the billionth time
問題描述
為了解決這個問題,我搜索了一下網站,當然還有更多的網站
- http://www.python.org/dev/peps/pep-0328/
- http://docs.python.org/2/tutorial/modules.html#packages
- Python packages: relative imports
- python relative import example code does not work
- Ultimate answer to relative python imports
- Relative imports in Python
- Python: Disabling relative import
這個問題是這樣的,在win7,32位的電腦上,運行python2.7.3,如何解決"Attempted relative import in non-package"問題。我根據pep-0328建立了一下的目錄結構
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
並且按照這個目錄當中的要求,建立了了spam和eggs函數。很顯然,當我運行它的時候,他並沒有運行成功。我列的第四個URL當中可能有一些答案,但是我還是理解不了。那個URL裏面的信息是這樣的:
相對導入使用模塊的名稱屬性來決定模塊在包層次結構中的位置,如果模塊的名稱不包含任何包信息(例如:被設置成
上面的答案看起來還是有點希望的,但是對於我來說還是有點理解不了。所以,我的問題是:如何讓我運行的python程序不再返回 "Attempted relative import in non-package",同時解釋一下-m選項。
有沒有人能夠告訴我python為什麽會報這個錯誤,這裏的‘non-package’是什麽意思,為什麽以及如何去定義一個‘package’。有沒有人能夠告訴我一些淺顯易懂的答案,好讓我這個剛入門的人能夠理解一些。
回答:
腳本VS模塊(script vs module)
直接運行一個文件和在別的文件中導入這個文件是有很大區別的,僅僅知道一個文件在目錄中的位置並不意味著python程序就認為它在什麽位置。這是由python用何種方式加載(運行或者導入.run or import)這個文件來決定的。
python有兩種加載文件的方法:一種是作為頂層的腳本,另一種時當做模塊。如果你直接執行這個程序,那麽這個文件就被當做是頂層腳本來執行了,在命令行裏面輸入 python myfile.py 就是這個情況。如果你輸入python -m myfile.py或者在其他的文件當中使用import來導入這個文件的時候,它就被當做模塊來導入。在同一時間裏,只有一個頂層腳本,頂層腳本是這樣的概念:它是一個能夠讓你的程序從這裏開始的python文件。
【文件是一種無區別的叫法,直接從這個文件運行,那麽這個文件就叫做腳本,導入這個文件,那麽這個文件就是模塊。包:是一個目錄,需要有__init__.py文件。模塊是module,包是package】
命名(naming)
當一個文件被加載進來,它就有一個名稱(這個名稱存儲在__name__屬性當中)。如果這個文件被當做一個頂層腳本來進行加載,那麽它的名字就是__main__【這就是為什麽我們經常會發現這樣的語句:if __name__ == "__main__"】。如果它被當做一個模塊加載,那麽它的名稱就是文件名稱,加上它所在的包名,以及所有的頂層的包名,這些名稱中間是用點號隔開的。
比如下面的例子
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
比如你導入moduleX(註:導入,不是直接運行),它的名稱就package.subpackage1.mouleX。如果你導入moduleA的時候,它的名稱就是package.moudleA。但是,當你直接從命令行裏面運行moduleA的時候,他的名稱則被替換為__main__。如果你直接從命令行運行moduleA,它的名稱也是__main__。當一個模塊被當做一個頂層腳本來執行的時候,它原來的名稱則會被__main__取代。
不通過包來訪問一個模塊
這裏有一個額外的問題:模塊的名稱取決於它是從它所在的目錄中直接導入的,還是通過包導入的。不過這種情況只會發生在你在一個目錄當中運行python,並且試圖導入這個目錄當中的文件的時候。【註:包導入是這樣的情況,別人封裝好了一個包(含有__init__.py文件),你直接通過包名.模塊名來使用這個模塊】。
舉個例子,如果你在package/subpackage1目錄當中打開python解釋器,然後輸入import moduleX,那麽moduleX的名稱就是moduleX,而不是package.subpackage1.moduleX。這是因為python把當前的目錄添加到了搜索路徑上面。如果它發現被包含的模塊在當前的目錄當中,它將不知道該目錄也是模塊的一部分,包的信息不會出現在模塊名稱當中。
一種特殊的情況是這樣的:當你直接運行python解釋器(比如,在命令行裏面輸入python,然後進入python解釋器,輸入代碼)。在這種情況下,這種交互式的終端的名稱是__main__.
現在,你的問題有了一個關鍵性的答案:如果一個模塊名稱當中沒有點號,那麽它就不會被當做是一個包。不管這個文件在磁盤的什麽位置。所有關鍵在於,它的名稱是什麽,而這個名稱取決於你如何加載它。
那麽現在看一下你在其他的URL當中的引用的這句話:
相對導入使用模塊的名稱屬性來決定模塊在包層次結構中的位置,如果模塊的名稱不包含任何包信息(例如:被設置成‘main’),那麽相對導入則被解析為最頂層的位置,不管這個時候這個模塊實際上位於文件系統中的什麽位置。
相對導入...
相對導入使用模塊的名稱去決定它在一個包中的位置。當你使用了一個像這樣的相對導入:from .. import foo,這裏的點號表明在包的層次結構當中上升幾個包層次。比如,現在模塊的名稱是package.subpackage1.moudleX,然後..moduleA中的兩個點號表示的是上升兩個包,到達package,然後package和moduleA結合,最終成為package.moduleA。要讓from .. import正常工作,模塊的名稱中必須至少有和上面相同多的點號數目。
【 舉個例子:模塊名稱:A.B.C.D
則from . import X 表示上升一層,然後和X結合,最終成為A.B.C.X
from .. import X 則是上升兩層,到達A.B,然後和X結合 ,結果為A.B.X 】
...只能用在相對導入當使用
如果你的模塊的名稱是__main__,那麽它就不被認為是在一個包當中,因為它的名稱當中不含有點,所以你不能在它的裏面使用from .. import。如果你使用了這個語句,那麽程序就會報“relative-import in non-package"錯誤。
腳本不能包含相對導入:
當你直接運行moduleX或者是在命令行終端裏運行程序的時候,這個時候模塊的名稱都是__main__,這就表明你不能使用相對導入。因為他們的名稱表示他們並不在一個包當中。註:當你運行python的目錄就是你模塊所在的目錄的時候,上面這種情況也會發生,這種情況下python過早的尋找當前目錄的模塊,並沒有認為他們也是包的一部分。
當你運行交互式的解釋器的時候,交互式進程的名稱永遠是__main__,因此你不能在交互式進程當中使用相對導入。相對導入只能在模塊文件當中使用。
兩個解決方法:
1:如果你想直接運行moduleX,但是你又想把它當做一個包的一部分,你可以使用python -m package.subpackage.moduleX. -m參數告訴python把它當做一個模塊來加載,而不是頂層的腳本。
2:或許你並不想直接運行moduleX,你想在其它的腳本當中使用moduleX的函數,比如說這個腳本是myfile.py。如果是這種情況,需要把myfile.py放在別的地方,而不是在package目錄裏面。在myfile.py使用一下語句就可以正常工作了:from package.moduleA import spam.
註:對於上面說的這兩種情況,包目錄(比如上面的package)必須存在於python的搜索路徑下面(sys.path)。如果不存在,你將不能夠使用包中的任何東西。
自從python2.6,模塊的名稱不在決定使用__name__屬性,而是使用__packege__屬性。這就是為什麽我避免使用__name__這麽明確的名稱來代表一個模塊的名稱。自從python2.6,一個模塊的名稱是由__package__+‘.‘+__name__來確定的,如果__packege__是None的話,那麽這個名稱就是__name__了。
python相對包導入報“Attempted relative import in non-package”錯誤