还有很多我没有复制的 URL,一些在 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 和 egg 的函数。自然,它没有奏效。答案显然在我列出的第 4 个 URL 中,但对我来说都是校友。我访问的其中一个 URL 上有这样的响应:
相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为’main’),那么无论模块实际位于文件系统上的什么位置,相对导入都会被解析为好像该模块是顶级模块一样。
上面的回复看起来很有希望,但对我来说都是象形文字。所以我的问题是,如何让 Python 不返回给我“尝试在非包中进行相对导入”?据说有一个涉及-m的答案。
有人可以告诉我为什么 Python 会给出该错误消息,“非包”是什么意思,为什么以及如何定义“包”,以及 用幼儿园儿童容易理解的术语给出的准确答案 。
脚本与模块
这是一个解释。简短的版本是直接运行 Python 文件和从其他地方导入该文件之间存在很大差异。 仅仅知道文件在哪个目录并不能确定 Python 认为它在哪个包中。 另外,这取决于您如何将文件加载到 Python 中(通过运行或通过导入)。
有两种方法可以加载 Python 文件:作为顶级脚本或作为模块。python myfile.py如果您直接执行文件,例如通过在命令行上键入,文件将作为顶级脚本加载。import当在其他文件中遇到语句时,它会作为模块加载。一次只能有一个顶级脚本;顶层脚本是你运行的 Python 文件。
python myfile.py
import
命名
当一个文件被加载时,它被赋予一个名称(存储在它的__name__属性中)。如果它是作为顶级脚本加载的,它的名字是__main__. 如果它作为一个模块加载,它的名称是文件名,前面是它所属的任何包/子包的名称,用点分隔。
__name__
__main__
因此,例如在您的示例中:
package/ __init__.py subpackage1/ __init__.py moduleX.py moduleA.py
如果您导入moduleX(注意: 导入 ,而不是直接执行),它的名称将是package.subpackage1.moduleX. 如果您导入moduleA,它的名称将是package.moduleA. 但是,如果您 直接从命令行运行 moduleX,它的名称将改为__main__,如果您直接从命令行运行moduleA,它的名称将是__main__. 当一个模块作为顶级脚本运行时,它会失去其正常名称,而改为__main__.
moduleX
package.subpackage1.moduleX
moduleA
package.moduleA
不通过其包含的包访问模块
还有一个额外的问题:模块的名称取决于它是从它所在的目录“直接”导入还是通过包导入的。仅当您在目录中运行 Python 并尝试在同一目录(或其子目录)中导入文件时,这才会有所不同。例如,如果您在目录中启动 Python 解释器,package/subpackage1然后执行import moduleX,则名称moduleX将只是moduleX,而不是package.subpackage1.moduleX。这是因为 Python 在交互输入解释器时会将当前目录添加到其搜索路径中;如果它在当前目录中找到要导入的模块,它不会知道该目录是包的一部分,包信息也不会成为模块名称的一部分。
package/subpackage1
import moduleX
一种特殊情况是,如果您以交互方式运行解释器(例如,只需键入python并即时开始输入 Python 代码)。在这种情况下,该交互式会话的名称是__main__。
python
现在这是您的错误消息的关键: 如果模块的名称没有点,则它不被认为是 package 的一部分 。文件在磁盘上的实际位置并不重要。重要的是它的名字是什么,它的名字取决于你如何加载它。
现在看看您在问题中包含的报价:
相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为’main’),那么无论模块实际位于文件系统上的哪个位置,都将相对导入解析为就好像该模块是顶级模块一样。
相对进口…
相对导入使用模块的 名称 来确定它在包中的位置。当您使用类似的相对导入from .. import foo时,圆点表示在包层次结构中提升一些级别。例如,如果您当前模块的名称是package.subpackage1.moduleX,则..moduleA表示package.moduleA. 为了使 afrom .. import起作用,模块的名称必须至少与import语句中的点一样多。
from .. import foo
..moduleA
from .. import
…仅在包中是相对的
但是,如果您的模块名称是__main__,则它不会被视为在包中。它的名字没有点,因此你不能from .. import在里面使用语句。如果您尝试这样做,您将收到“非包中的相对导入”错误。
脚本无法导入相对
您可能所做的是尝试从命令行运行moduleX或类似操作。当你这样做时,它的名字被设置为__main__,这意味着它里面的相对导入将失败,因为它的名字并没有表明它在一个包中。请注意,如果您从模块所在的同一目录运行 Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python 会“过早”在当前目录中找到该模块而没有意识到它是包的一部分。
还要记住,当您运行交互式解释器时,该交互式会话的“名称”始终是__main__. 因此 ,您不能直接从交互式会话中进行相对导入 。相对导入仅用于模块文件中。
两种解决方案:
如果您确实想moduleX直接运行,但仍希望将其视为包的一部分,则可以执行python -m package.subpackage1.moduleX. 告诉 Python将-m其作为模块加载,而不是作为顶级脚本加载。
python -m package.subpackage1.moduleX
-m
或者,也许您实际上并不想 运行 moduleX,您只想运行一些其他脚本,例如myfile.py, 使用 内部函数的脚本moduleX。如果是这种情况,请将其放在myfile.py 其他 位置( 不在package目录中)并运行它。如果myfile.py你在里面做类似的事情from package.moduleA import spam,它会工作得很好。
myfile.py
package
from package.moduleA import spam
笔记
对于这些解决方案中的任何一个,包目录(package在您的示例中)必须可以从 Python 模块搜索路径 ( sys.path) 访问。如果不是,您将根本无法可靠地使用包中的任何内容。
sys.path
从 Python 2.6 开始,用于包解析目的的模块“名称”不仅取决于其__name__属性,还取决于__package__属性。这就是为什么我避免使用显式符号__name__来引用模块的“名称”。由于 Python 2.6 模块的“名称”是有效的__package__ + '.' + __name__,或者只是__name__如果__package__是None。)
__package__
__package__ + '.' + __name__
None