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

原文出處: http://stackoverflow.com/   譯文出處:yibohu1899   

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

腳本VS模塊

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

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

名字

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

所以,看看你這裏的例子

1

2

3

4

5

6

7

package/

__init__.py

subpackage1/

    __init__.py

    moduleX.py

moduleA.py

enter code here

如果你導入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/

1

2

3

4

5

__init__.py

subpackage1/

    __init__.py

    moduleX.py

moduleA.py

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:

1

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 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:

1

2

3

If you really do want to run moduleX directly, but you still want it to be considered part of a package, you can do python -m package.subpackage.moduleX. The -m tells Python to load it a s a module, not as the top-level script.

 

Or perhaps you don't actually want to run moduleX, you just want to run some other script, say myfile.py, that uses functions inside moduleX. If that is the case, put myfile.py somewhere else --- not inside the package directory -- and run it. If inside myfile.py you do things like from package.moduleA import spam, it will work fine.

Notes

1

2

3

For either of these solutions, the package directory (package in your example) must be accessible from the Python module search path (sys.path). If it is not, you will not be able to use anything in the package reliably at all.

 

since Python 2.6, the module's "name" for package-resolution purposes is

QQ技術交流羣290551701  http://cxy.liuzhihengseo.com/552.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章