Python相对导入导致SystemError的解决方案(译)

390 查看

这个问题是如何解决在相对导入的时候,如果出现’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