Python相對匯入機制詳解
本文是對 http://stackoverflow.com/questions/14132789/python-relative-imports-for-the-billionth-time#answer-14132912 這個 SO 答案的翻譯。本人的翻譯一向只追求含義準確而不追求字字對應,有些不好翻的術語或者固定說法就直接保留。
這個答案能解釋大多關於 relative import,即相對匯入的疑惑,講解十分詳盡清晰,算是 SO 上被低估的一個答案。
問題不翻譯了,直接摘錄下來:
The forever-recurring question is this: With Windows 7, 32-bit Python 2.7.3, how do I solve this "Attempted relative import in non-package" message? I built an exact replica of the package on pep-0328:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
I did make functions named spam and eggs in their appropriate modules. Naturally, it didn't work. The answer is apparently in the 4th URL I listed, but it's all alumni to me. There was this response on one of the URLs I visited:
Relative imports use a module's name attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to'main') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.
The above response looks promising, but it's all hieroglyphs to me. So my question, how do I make Python not return to me"Attempted relative import in non-package"? has an answer that involves -m, supposedly.
Can somebody please tell me why Python gives that error message, what it Means by non-package!, why and how do you define a 'package', and the precise answer put in terms easy enough for a kindergartener to understand. Thanks in advance!
Edit: The imports were done from the console.
BrenBarn 的精彩答案(這個哥們可以算是 import 專家了,答了好多這方面的題)
簡單地說,直接執行 .py 檔案和 import 這個檔案有很大區別。Python 直譯器判斷一個 py 檔案屬於哪個 package 時並不完全由該檔案所在的資料夾決定。它還取決於這個檔案是如何 load 進來的(直接執行 or import)。
有兩種方式載入一個 py 檔案:作為 top-level 指令碼或者作為 module。前者指的是直接執行指令碼,比如 python myfile.py
。如果執行 python -m myfile
,或者在其它 py 檔案中用 import
語句來載入,那麼它就會被當作一個 module。有且只能有一個 top-level 指令碼,就是最開始執行的那個(比如 python myfile.py
中的 myfile.py
,譯者注)。
當一個 py 檔案被載入之後,它會被賦予一個名字,儲存在 __name__
屬性中。如果是 top-level 指令碼,那麼名字就是 __main__
。如果是作為 module,名字就是把它所在的 packages/subpackages 和檔名用 .
連線起來。
例如,moduleX
被 import 進來,它的名字就是 package.subpackage1.moduleX
。如果 import 了 moduleA
,它的名字是 package.moduleA
。如果直接執行 moduleX
或 moduleA
,那麼名字就都是 __main__
了。
另一個令人擔憂的問題是,一個 module 的名字取決於它是直接從它所在的資料夾 import 還是通過某個 package import 的。不過只有當你在某個路徑中執行 Python 並試圖從當前資料夾 import 一個 py 檔案時,才需要關注它們的不同。例如,在路徑 pacakge/subpackage1
中執行 python 直譯器,然後指令碼中有 import moduleX
這個語句,此時 moduleX
的名字正是 moduleX
,而不是 package.subpackage1.moduleX
。這是因為 Python 直譯器在啟動時把當前路徑(這裡答案寫的不準確,其實加入的是 top-level 指令碼的路徑,因為兩者在這種狀況下相同,所以也並不算錯。譯者注)加入了它的搜尋路徑 (sys.path);如果發現要 import 的 module 就在當前路徑,那麼 Python 直譯器就不知道當前路徑是屬於哪個 package 的,所以 pacakge 的資訊就不會成為 module 的名字的一部分。
一個特例是直接執行 python REPL,這個 REPL 的 session 的名字是 __main__
。
關於你遇到的錯誤資訊,關鍵點來了:如果一個 module 的名字中沒有點(即 package.subpackage1 中的那個點,譯者注),那麼它就被認為不屬於任何一個 package。檔案在磁碟上的位置在哪裡都不影響,唯一起決定作用的就是 module 的名字,而這又取決於它是如何被載入的。
先在我們看看你在問題中引用的這段話
Relative imports use a module's name attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to'main') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.
relative import 使用 module 的名字來決定它是否屬於一個 package,屬於哪個 package。當你使用這種 relative import from .. import foo
,其中的點的數量代表了 package 結構中的某個層次。例如,如果當前 module 的名字是 package.subpackage1.moduleX
,那麼 ..moduleA
代表 package.moduleA
。為了讓形如 from .. import
的這種匯入能夠正常工作,module 的名字裡的點數量應當至少和 import 語句中一樣多。
前面說了,如果 module 的名字是 __main__
,那麼 Python 就不認為它屬於某個 package。由於名字裡不包含點,所以在這個 py 檔案中 from .. import
語句無法正常工作。試圖執行這條語句就會報 "relative-import in non-package" 錯誤。
你犯的錯誤可能是從命令列執行 moduleX
或類似的操作。當你執行這個操作,moduleX 的名字被設定成 __main__
,所以 relative imports 失敗了,因為不包含 package 資訊。正如前面說的,如果在同一個路徑裡 import 一個檔案,這時 module 的名字就是檔名,不包含 package 資訊,所以相對匯入也會失敗。
記住,因為 REPL session 的名字總是 __main__
,所以試圖在 REPL 裡執行 relative import 是不行的。relative import 應當只在 module 檔案中被使用。
(無法相對匯入的問題)有兩個解決方法。如果你真的想直接執行 moduleX
,同時又希望它被當作所在 package 的一部分,可以這麼做:python -m package.subpackage.moduleX
。-m
引數告訴 Python 直譯器,把這個檔案當作一個 module 載入,而不是 top-level 指令碼。
如果你並不想直接執行 moduleX
,而是想在另一個檔案比如 myfile.py
中使用 moduleX
中定義的函式,那麼解決方法是把 myfile.py
檔案挪到另一個地方,只要不在 moduleX
所屬的 package 的資料夾裡就行。然後在 myfile.py
中執行 from package.moduleA import spam
,就能正常工作了。
注意,不論哪種解決方案,都需要 package 的路徑(上文中的 package
)在 python 的搜尋路徑也就是 sys.path
裡。如果不在,那麼就無法使用這個 package 中的任何檔案。
(更嚴謹的說明:從 Python2.6 開始,在做 package-resolution 時,module 的 “名字” 並不完全等於 __name__
屬性,還和 __package__
屬性有關。這也是為什麼上文中我一直儘量避免用 __name__
來指代 module 的名字。從 python2.6 開始,一個 module 的 “名字” 實際上是 __package__ + '.' + __name__
, 或者直接就是 __name__
,如果 __package__
是 None
的話)