Python相對匯入導致SystemError的解決方案(譯)

發表於2016-04-18

這個問題是如何解決在相對匯入的時候,如果出現’System Error’的時候的解決方案。順帶一提,這個問題好像出在原始碼上,在issue 18018得到解決,附上這個據說可以解決問題的地址:解決方案。我不知道怎麼使用,希望知道的讀者(如果有的話)可以告訴我~

指令碼VS模組

這裡是解釋。長話短說,這是因為在執行一個Python檔案和從什麼地方匯入那個檔案之間,存在一個巨大的差異。你只要明白:一個檔案到底在哪裡並不能決定Python覺得它在哪個包裡面。此外,這實際上取決於你是如何將這個檔案匯入到Python中的

匯入一個Python檔案有兩種方法:作為頂層檔案,或者作為一個模組。如果你直接執行它,這個檔案就會被當作頂層檔案來執行,比如在命令列中輸入python myfile.py。如果你使用python -m myfile,或者它是通過其他檔案的import語句匯入的,那這個檔案就會作為一個模組被匯入。一次只能有一個頂層檔案;頂層檔案是你一開始執行的Python檔案。

名字

當一個檔案被匯入的時候,它會得到一個名字(儲存在其__name__屬性中)。如果它是作為頂層檔案被匯入的,那麼它的__name__就是__main__;如果它是作為一個模組被匯入的,則它的__name__就是它的檔名,先於任何它所組成的包或子包,由點號分開。

所以,看看你這裡的例子

如果你匯入moduleX(注意,匯入而非執行),那麼它的名字就是package.subpackage1.moduleX。如果你匯入moduleA,則它的名字就是package.moduleA。可是,如果你直接從命令列執行moduleX,則它的名字就會被__main__取而代之,moduleA也是如此。當一個模組作為頂層檔案被執行的時候,其會失去本身的名字,並由__main__取而代之。

不通過包含它的包來獲取一個模組

這裡有一個附送的小技巧:模組名取決於它是從所在的目錄直接匯入,或者通過一個包匯入的。這隻有在你從目錄中執行Python,以及嘗試在相同的目錄(或者它的子目錄)匯入一個檔案的時候有差別。舉個例子,如果你在目錄package/subpackage1執行Python直譯器,然後匯入moduleX,moduleX的名字就只會是moduleX,而不是package.subpackage1.moduleX。這是因為,Python將當前的路徑新增到模組搜尋路徑的開頭;如果它發現當前目錄有一個需要執行的模組,它不會明白這個目錄是包的一部分,而這個包的資訊也不會成為模組名的一部分。

如果你只是互動式地執行直譯器,會出現一個特殊情況(比如:輸入python並進入shell)。這種情況下,這個互動式會話的名字是__main__

你的錯誤實際上是:如果一個模組名沒有點,它不會被視作包的一部分。這個檔案實際上在哪個目錄無關緊要。唯一相關的是,它的名字是什麼,而它的名字取決於你是如何匯入它的。

現在,看看你在問題中所引用的內容:

相對匯入使用一個模組的__name__屬性來決定模組在包中的層次。如果模組的名字不包含任何模組資訊(比如被設定為__main__),那麼相對匯入將會把該模組視作頂層模組,忽視其在檔案系統中的實際位置。

相對匯入……

相對匯入使用模組的__name__決定它是否在一個包內。當你是用類似form .. import foo進行相對匯入的時候,點表明在包的層次中上升多少。舉個例子,如果你當前的模組的名字是package.subpackage1.moduleX,那麼..moduleA就表示模組package.moduleA。要想讓from .. import語句起作用,模組的名字至少有在import語句中的點的數量。

… 只是在包中是相對的

可還是,如果你的模組名是__main__,那麼它不會被當作一個包。它的名字裡面沒有點,因此你不能在檔案內使用from .. import 語句。如果你嘗試這麼幹,你會獲得錯誤relative-import in non-package

指令碼不可以相對地匯入

你想做的事情大概是嘗試要從命令列中執行moduleX。當你這麼幹的時候,它的名字會被設定為__main__,這意味著裡面的相對匯入失效了,因為:它的名字並沒有顯示出它在一個包裡面。注意,這會發生在你從相同的目錄執行中執行Python並試圖匯入那個模組的時候,這是因為:如上所說,python會在意識到這是包的一部分之前,在當前目錄下“過早”找到那個模組。

你也要記得,當你執行一個互動式直譯器的時候,互動式會話的name總是__main__。所以,你不能在互動式會話中直接使用任何相對匯入。相對匯入只能使用在模組檔案內。

兩個解決方案:

  1. 如果你真的向直接執行moduleX,但你希望它可以被視作包的一部分,那使用python -m package.subpackage.moduleX執行即可。選項-m告訴Python將其作為一個模組而非頂層檔案匯入。
  2. 如果你不不希望真的執行moduleX,你只想執行其他使用了在moduleX裡的函式的指令碼,如myfile.py。在這種嗯情況下,將myfile.py放到其他地方——不在包目錄裡面——並執行它。如果在myfile.py裡面,你做點類似從package.moduleA裡面匯入spam,它會做的很好~

注意事項:

對於這兩個解決方法的任意一個來說,包目錄必須在模組搜尋路徑sys.path中。如果不是的話,你不能確實可靠地使用包裡面的任何東西。
自從Python2.6以來,包依賴的解決不僅僅取決於模組的名字,還有模組的__package屬性。這使我為什麼避免使用__name__來指代模組的名字。一個模組的名字現在可能是__package__ + __name__了,除非沒有包。

原文如下:
Script vs. Module

Here’s an explanation. The short version is that there is a big difference between directly running a Python file, and importing that file from somewhere else. Just knowing what directory a file is in does not determine what package Python thinks it is in. That depends, additionally, on how you load the file into Python (by running or by importing).

There are two ways to load a Python file: as the top-level script, or as a module. A file is loaded as the top-level script if you execute it directly, for instance by typing python myfile.py on the command line. It is loaded as a module if you do python -m myfile, or if it is loaded when an import statement is encounted inside some other file. There can only be one top-level script at a time; the top-level script is the Python file you ran to start things off.

Naming

When a file is loaded, it is given a name (which is stored in its name attribute). If it was loaded as the top-level script, its name is __main__. If it was loaded as a module, its name is the filename, preceded by the names of any packages/subpackages of which it is a part, separated by dots.

So for instance in your example:

package/

enter code here

if you imported moduleX (note: imported, not directly executed), its name would be package.subpackage1.moduleX. If you imported moduleA, its name would be package.moduleA. However, if you directly run moduleX from the command line, its name will instead be __main__, and if you directly run moduleA from the command line, its name will be __main__. When a module is run as the top-level script, it loses its normal name and its name is instead __main__.

Accessing a module NOT through its containing package

There is an additional wrinkle: the module’s name depends on whether it was imported “directly” from the directory it is in, or imported via a package. This only makes a difference if you run Python in a directory, and try to import a file in that same directory (or a subdirectory of it). For instance, if you start the Python interpreter in the directory package/subpackage1 and then do import moduleX, the name of moduleX will just be moduleX, and not package.subpackage1.moduleX. This is because Python adds the current directory to its search path on startup; if it finds the to-be-imported module in the current directory, it will not know that that directory is part of a package, and the package information will not become part of the module’s name.

A special case is if you run the interpreter interactively (e.g., just type python and start entering Python code on the fly). In this case the name of that interactive session is __main__.

Now here is the crucial thing for your error message: if a module’s name has no dots, it is not considered to be part of a package. It doesn’t matter where the file actually is on disk. All that matters is what its name is, and its name depends on how you loaded it.

Now look at the quote you included in your question:

Relative imports…

Relative imports use the module’s name to determine where it is in a package. When you use a relative import like from .. import foo, the dots indicate to step up some number of levels in the package hierarchy. For instance, if your current module’s name is package.subpackage1.moduleX, then ..moduleA would mean package.moduleA. For a from .. import to work, the module’s name must have at least as many dots as there are in the import statement.

… are only relative in a package

However, if your module’s name is __main__, it is not considered to be in a package. Its name has no dots, and therefore you cannot use from .. import statements inside it. If you try to do so, you will get the “relative-import in non-package” error.

Scripts can’t import relative

What you probably did is you tried to run moduleX or the like from the command line. When you did this, its name was set to __main__, which means that relative imports within it will fail, because its name does not reveal that it is in a package. Note that this will also happen if you run Python from the same directory where a module is, and then try to import that module, because, as described above, Python will find the module in the current directory “too early” without realizing it is part of a package.

Also remember that when you run the interactive interpreter, the “name” of that interactive session is always __main__. Thus you cannot do relative imports directly from an interactive session. Relative imports are only for use within module files.

Two solutions:

Notes

相關文章