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__了。

相关文章
相关标签/搜索