python相對包導入報「Attempted relative import in non-package」錯誤

文章是從stackoverflow翻譯過來的,原文地址:Relative imports for the billionth timehtml

本文要在原理上解決  python當中相對包導入出現的問題。python

問題描述

在win七、32位的電腦上,運行python2.7.3,常常會出現"Attempted relative import in non-package"這樣的問題。python2.7

爲了解決這個問題,我(提問的人)搜索瞭如下網站,固然還有更多的網站函數

 

我根據pep-0328創建瞭如下的目錄結構網站

package/

    __init__.py

        subpackage1/

            __init__.py

            moduleX.py

            moduleY.py

        subpackage2/

            __init__.py

            moduleZ.py

        moduleA.py

並且按照這個目錄當中的要求,創建了了spam( moduleY.py中和eggs(moduleZ.py中)函數,如今想要在moduleX.py當中調用別的函數或模塊。很顯然,當我運行的時候,並無成功。spa

上面的第四個URL當中有以下信息,這個答案比較接近真相,可是其中的有些概念仍是很難理解:命令行

相對導入使用模塊的名稱屬性來決定模塊在包層次結構中的位置,若是模塊的名稱不包含任何包信息(例如:被設置成‘main’),那麼相對導入則被解析爲最頂層的位置,無論這個時候這個模塊實際上位於文件系統中的什麼位置。翻譯

因此,如何讓我運行的python程序再也不返回 "Attempted relative import in non-package"問題?python爲何會報這個錯誤?這裏的‘non-package’是什麼意思?爲何以及如何去定義一個‘package’?同時解釋一下-m選項?code

 


 回答:

什麼是腳本?什麼是模塊?(script vs module)

直接運行一個文件和在別的文件中導入這個文件是有很大區別的,僅僅知道一個文件在目錄中的位置並不意味着python程序就認爲它在什麼位置。這是由python用何種方式加載(運行或者導入.run or import)這個文件來決定的。htm

python有兩種加載文件的方法:一種是做爲頂層的腳本,另外一種是當作模塊。若是你直接執行這個程序,那麼這個文件就被當作是頂層腳原本執行了,在命令行裏面輸入 python myfile.py 就是這個狀況。若是你輸入python -m myfile.py或者在其餘的文件當中使用import來導入這個文件的時候,它就被當作模塊來導入。在同一時間裏,只有一個頂層腳本,頂層腳本能夠這樣解釋:它是一個可以讓你的程序從這裏開始的python文件。

【文件(file)是一種無區別的叫法,如何運行和處理這個文件,決定了它的性質。直接從這個文件運行,那麼這個文件就叫作腳本。導入這個文件,那麼這個文件就是模塊(module)。另外,一個包(package)是一個包含有__init__.py的文件夾,下面會用到】

 

命名(naming)

當一個文件被加載進來,它就有一個名稱(這個名稱存儲在__name__屬性當中)。若是這個文件被當作一個頂層腳原本進行加載,那麼它的名字就是__main__。若是它被當作一個模塊加載,那麼它的名稱就是文件名稱,加上它所在的包名,以及全部的頂層的包名,這些名稱中間是用點號隔開的。

好比下面的例子

package/

    __init__.py

    subpackage1/

        __init__.py

        moduleX.py

    moduleA.py 

好比你導入moduleX(from package.subpackag1 import moduleX),它的名稱就package.subpackage1.mouleX。若是你導入moduleA的時候(from package import moduleA),它的名稱就是package.moudleA。

(注:這裏是使用包導入,即把package以及裏面的全部文件看作一個包,導入的時候使用from ... import ...的形式來進行,咱們調用第三方包的時候就是這種狀況),

可是,當你直接從命令行裏面運行moduleX的時候,他的名稱則被替換爲__main__。若是你直接從命令行運行moduleA,它的名稱也是__main__。當一個模塊被當作一個頂層腳原本執行的時候,它原來的名稱則會被__main__取代。

 

不經過包導入訪問一個模塊

這裏有一個額外的問題:模塊的名稱取決於從它所在的目錄中直接導入的,仍是經過包導入的。這種狀況只有在該包中運行python文件,而且試圖導入這個包當中的其它文件的時候纔有意義。(能夠發現,上面的介紹的包導入老是在外面來訪問一個模塊)

舉個例子,若是你在package/subpackage1目錄當中打開python解釋器,而後輸入import moduleX,那麼moduleX的名稱就是moduleX,而不是package.subpackage1.moduleX。這是由於python把當前的目錄添加到了搜索路徑上面。若是它發現被包含的模塊在當前的目錄當中,它將不知道該目錄也是模塊的一部分,包的信息不會出如今模塊名稱當中。

當你直接運行python解釋器(好比cmd裏面輸入python,而後進入python解釋器,或者使用ipython)。在這種狀況下,這種交互式的終端的名稱是__main__。

 

如今,你的問題有了一個關鍵性的答案:若是一個模塊名稱當中沒有點號,那麼它就不會被當作是一個包。無論這個文件在磁盤的什麼位置。全部關鍵在於,它的名稱是什麼,而這個名稱取決於你如何加載它。

那麼如今看一下你在其餘的URL當中的引用的這句話:

相對導入使用模塊的名稱屬性來決定模塊在包層次結構中的位置,若是模塊的名稱不包含任何包信息(例如:被設置成‘main’),那麼相對導入則被解析爲最頂層的位置,無論這個時候這個模塊實際上位於文件系統中的什麼位置。

 

相對導入...

相對導入使用模塊的名稱去決定它在一個包中的位置。當你使用了一個像這樣的相對導入:from .. import foo,這裏的點號代表在包的層次結構當中上升幾個層級。好比,如今模塊的名稱是package.subpackage1.moudleX,而後..moduleA中的兩個點號表示的是上升兩個層級,到達package,而後package和moduleA結合,最終成爲package.moduleA。要讓from .. import這樣的相對導入正常工做,模塊的名稱中至少要有和語句中相對應的「點」的數量。

 

...只能用在相對導入當使用

若是你的模塊的名稱是__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__了。

相關文章
相關標籤/搜索