小编典典

十亿次相对进口

all

还有很多我没有复制的 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 会给出该错误消息,“非包”是什么意思,为什么以及如何定义“包”,以及 用幼儿园儿童容易理解的术语给出的准确答案


阅读 100

收藏
2022-02-25

共1个答案

小编典典

脚本与模块

这是一个解释。简短的版本是直接运行 Python 文件和从其他地方导入该文件之间存在很大差异。 仅仅知道文件在哪个目录并不能确定 Python
认为它在哪个包中。
另外,这取决于您如何将文件加载到 Python 中(通过运行或通过导入)。

有两种方法可以加载 Python 文件:作为顶级脚本或作为模块。python myfile.py如果您直接执行文件,例如通过在命令行上键入,文件将作为顶级脚本加载。import当在其他文件中遇到语句时,它会作为模块加载。一次只能有一个顶级脚本;顶层脚本是你运行的
Python 文件。

命名

当一个文件被加载时,它被赋予一个名称(存储在它的__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
并尝试在同一目录(或其子目录)中导入文件时,这才会有所不同。例如,如果您在目录中启动 Python
解释器,package/subpackage1然后执行import moduleX,则名称moduleX将只是moduleX,而不是package.subpackage1.moduleX。这是因为 Python
在交互输入解释器时会将当前目录添加到其搜索路径中;如果它在当前目录中找到要导入的模块,它不会知道该目录是包的一部分,包信息也不会成为模块名称的一部分。

一种特殊情况是,如果您以交互方式运行解释器(例如,只需键入python并即时开始输入 Python
代码)。在这种情况下,该交互式会话的名称是__main__

现在这是您的错误消息的关键: 如果模块的名称没有点,则它不被认为是 package 的一部分
。文件在磁盘上的实际位置并不重要。重要的是它的名字是什么,它的名字取决于你如何加载它。

现在看看您在问题中包含的报价:

相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为’main’),那么无论模块实际位于文件系统上的哪个位置,都将相对导入解析为就好像该模块是顶级模块一样。

相对进口…

相对导入使用模块的 名称 来确定它在包中的位置。当您使用类似的相对导入from .. import foo时,圆点表示在包层次结构中提升一些级别。例如,如果您当前模块的名称是package.subpackage1.moduleX,则..moduleA表示package.moduleA.
为了使 afrom .. import起作用,模块的名称必须至少与import语句中的点一样多。

…仅在包中是相对的

但是,如果您的模块名称是__main__,则它不会被视为在包中。它的名字没有点,因此你不能from .. import在里面使用语句。如果您尝试这样做,您将收到“非包中的相对导入”错误。

脚本无法导入相对

您可能所做的是尝试从命令行运行moduleX或类似操作。当你这样做时,它的名字被设置为__main__,这意味着它里面的相对导入将失败,因为它的名字并没有表明它在一个包中。请注意,如果您从模块所在的同一目录运行
Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python 会“过早”在当前目录中找到该模块而没有意识到它是包的一部分。

还要记住,当您运行交互式解释器时,该交互式会话的“名称”始终是__main__. 因此 ,您不能直接从交互式会话中进行相对导入
。相对导入仅用于模块文件中。

两种解决方案:

  1. 如果您确实想moduleX直接运行,但仍希望将其视为包的一部分,则可以执行python -m package.subpackage1.moduleX. 告诉 Python将-m其作为模块加载,而不是作为顶级脚本加载。

  2. 或者,也许您实际上并不想 运行 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__,或者只是__name__如果__package__None。)

2022-02-25