我如何修复ImportError:尝试相对于顶级包以外的相对导入错误。
我如何修复ImportError:尝试相对于顶级包以外的相对导入错误。
我来过这些地方:
- http://www.python.org/dev/peps/pep-0328/
- http://docs.python.org/2/tutorial/modules.html#packages
- Python packages: relative imports
- python relative import example code does not work
- Python 2.5 中的相对导入
- Python 中的相对导入
- Python:禁用相对导入
还有很多我没复制的网址,有些在SO上,有些在其他网站上,当我以为我会很快找到解决方法的时候。
永远反复出现的问题是:我如何解决“在非包中尝试相对导入”的消息?
ImportError: attempted relative import with no known parent package
我建立了一个完全复制pep-0328包的副本:
package/ __init__.py subpackage1/ __init__.py moduleX.py moduleY.py subpackage2/ __init__.py moduleZ.py moduleA.py
导入是从控制台完成的。
我确实在它们相应的模块中制作了名为spam和eggs的函数。自然地,它没有起作用。答案显然在我列出的第4个网址中,但对我来说它们都是校友。我访问的网址中有这样的回复:
相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,设置为“main”),那么无论模块实际上位于文件系统的位置如何,相对导入都将解析为顶层模块。
上述回复看起来很有希望,但对我来说都是象形文字。所以我的问题是,如何让Python不返回“在非包中尝试相对导入”这个错误消息,其中答案涉及-m,据说。
请问有人能告诉我为什么Python会给出这个错误消息,它所说的“非包”是什么意思,为什么以及如何定义一个“包”,以及用易于幼儿理解的术语给出精确答案。
这实际上是 Python 中的一个问题。混淆的来源在于人们错误地将相对导入视为路径相对导入,而这并非如此。
例如,当您在 faa.py 中编写以下内容:
from .. import foo
只有当 python 在执行期间将 faa.py 作为包的一部分识别并加载时,这才有意义。在那种情况下,faa.py 的模块名称将是例如 some_package_name.faa。如果该文件仅因为它在当前目录中而被 python 运行时加载,则其名称不会引用任何包,并且最终相对导入将失败。
引用当前目录中的模块的一个简单解决方案是使用:
if __package__ is None or __package__ == '': # uses current directory visibility import foo else: # uses current package visibility from . import foo
脚本 vs. 模块
下面是一个解释。简而言之,直接运行Python文件与从其他地方导入该文件之间存在很大的差别。仅知道文件所在的目录并不能确定Python认为它属于哪个包。此外,这还取决于您如何将文件加载到Python中(通过运行或导入)。
有两种加载Python文件的方式:作为顶层脚本或作为模块。如果您直接执行文件,例如在命令行上键入python myfile.py
,则会将文件加载为顶层脚本。如果在其他文件中遇到import
语句,则会将其作为模块加载。每次只能有一个顶层脚本;顶层脚本是您运行以启动事物的Python文件。
命名
加载文件时,它会被赋予一个名称(存储在其__name__
属性中)。
- 如果将其作为顶层脚本加载,则其名称为
__main__
。 - 如果将其作为模块加载,则其名称为[文件名,前缀是其所属的任何包/子包的名称,用点号分隔],例如
package.subpackage1.moduleX
。
但是请注意,如果您从shell命令行作为模块加载moduleX
,例如使用python -m package.subpackage1.moduleX
,那么__name__
仍将是__main__
。
因此,在您的例子中:
package/ __init__.py subpackage1/ __init__.py moduleX.py moduleA.py
如果您导入了moduleX
(注意:导入的是,而不是直接执行),它的名称将会是package.subpackage1.moduleX
。如果您导入了moduleA
,它的名称将会是package.moduleA
。然而,如果您直接从命令行运行moduleX
,则它的名称将变成__main__
,如果您直接从命令行运行moduleA
,则它的名称将变成__main__
。当一个模块作为顶级脚本运行时,它会失去普通的名称,其名称将变成__main__
。
不通过其包访问一个模块
这里还有一个额外的细节:模块的名称取决于它是直接从所在的目录中导入还是通过包导入的。这只有在您在一个目录中运行Python并试图导入该目录中的文件(或其子目录中的文件)时才有所区别。例如,如果您在目录package/subpackage1
中启动Python解释器,然后执行import moduleX
,则moduleX
的名称将只是moduleX
,而不是package.subpackage1.moduleX
。这是因为在交互式输入解释器时,Python会将当前目录添加到搜索路径中;如果它在当前目录中找到将要导入的模块,它将不知道该目录是一个包的一部分,因此包信息将不会成为模块名称的一部分。
如果您交互式地运行解释器(例如,只输入python
并开始即时输入Python代码),那么该交互式会话的名称将是__main__
。
对于你的错误信息,以下是关键点:如果模块的名称没有点,那么它不被认为是包的一部分。它实际上在磁盘上的位置并不重要,重要的是它的名称以及它的名称取决于你如何载入它。
现在看看你提问中引用的内容:
相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为 "main"),则相对导入将解析为如果该模块是顶级模块,而实际上它在文件系统中的位置无关紧要。
相对导入...
相对导入使用模块的名称来确定它在包中的位置。当你使用相对导入,例如from .. import foo
,点表示在包层次结构中上移一些级别。例如,如果你当前的模块名称是package.subpackage1.moduleX
,那么..moduleA
意味着package.moduleA
。对于from .. import
语句,模块的名称必须至少包含与import
语句中的点数相同。
...仅在包中是相对的
然而,如果你的模块名称是__main__
,它不被认为是在一个包内。它的名称没有点,因此你不能在该模块内使用from .. import
语句。如果你尝试这样做,就会出现 "relative-import in non-package"错误。
脚本无法使用相对路径导入
你可能尝试从命令行运行moduleX
或类似的模块。当你这样做时,它的名称被设置为__main__
,这意味着其中的相对导入将会失败,因为它的名称没有显示它在一个包中。注意,当你从相同的目录运行Python并尝试导入该模块时,这也会发生,因为如上所述,Python会在不意识到它是包的一部分的情况下“过早地”在当前目录中找到该模块。
还要记住,当你运行交互式解释器时,该交互式会话的“名称”始终为__main__
。因此你不能直接从交互式会话进行相对导入。相对导入仅适用于模块文件内部。
两种解决方法:
-
如果你确实想直接运行
moduleX
,但仍希望它被视为包的一部分,你可以使用python -m package.subpackage1.moduleX
。-m告诉Python将其作为模块而不是顶级脚本加载。 -
或者你实际上并不想运行
moduleX
,而是想运行其他脚本,比如myfile.py
,该脚本使用moduleX
内的函数。如果是这种情况,请将myfile.py
放在别处-而不是package
目录中-并运行。如果在myfile.py
中做诸如from package.moduleA import spam
之类的操作,它将正常工作。
注意事项
-
对于这两种解决方案中,包目录(在你的例子中为
package
)必须能够从Python模块搜索路径(sys.path
)访问。如果不能访问,你将无法可靠地使用包中的任何内容。 -
自Python 2.6以来,模块的用于解析包的“名称”不仅由其
__name__
属性确定,还由__package__
属性确定。这就是为什么避免使用显式符号__name__
来引用模块的“名称”的原因。自Python 2.6以来,模块的“名称”有效地是__package__ + '.' + __name__
,如果__package__
为None
,就只是__name__
。