第十亿次相对进口

我来过这里:

还有很多我没有复制的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和eggs的函数。当然,它没有奏效。答案显然在我列出的第4个URL中,但对我来说都是校友。在我访问的其中一个URL上有这样的响应:

相对导入使用模块的 name 属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为“main”),则相对导入会被解析为模块是顶级模块,而不管模块在文件系统上的实际位置如何。

上面的回应看起来很有希望,但对我来说都是象形文字。所以我的问题,我如何使Python不返回给我“尝试在非包中相对导入”?据说有一个涉及-m的答案。

有人可以告诉我为什么Python给出这个错误消息,“非包”是什么意思,为什么以及如何定义“包”,以及准确的答案,术语足够容易让幼儿园的孩子理解


答案 1

脚本与模块

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

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

命名

加载文件时,会为其指定一个名称(存储在其属性中)。__name__

  • 如果它作为顶级脚本加载,则其名称为 。__main__
  • 如果它作为模块加载,则其名称为 [ 文件名,前面是它所属的任何包/子包的名称,用点分隔 ],例如 。package.subpackage1.moduleX

但请注意,如果您使用类似的东西从 shell 命令行加载为模块,则仍将是 。moduleXpython -m package.subpackage1.moduleX__name____main__

例如,在您的示例中:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

如果导入(注意:导入,不直接执行),则其名称将为 。如果导入 ,则其名称将为 。但是,如果直接从命令行运行,则其名称将为 ,如果直接从命令行运行,则其名称将为 。当模块作为顶级脚本运行时,它将丢失其正常名称,而其名称为 。moduleXpackage.subpackage1.moduleXmoduleApackage.moduleAmoduleX__main__moduleA__main____main__

访问模块时,不要通过其包含的包

还有一个额外的问题:模块的名称取决于它是从它所在的目录中“直接”导入的还是通过包导入的。这只有在目录中运行Python并尝试在同一目录(或其子目录)中导入文件时才会有所不同。例如,如果您在目录中启动Python解释器,然后执行,则 的名称将只是 ,而不是 。这是因为当以交互方式输入解释器时,Python会将当前目录添加到其搜索路径中;如果它在当前目录中找到要导入的模块,它将不知道该目录是包的一部分,并且包信息不会成为模块名称的一部分。package/subpackage1import moduleXmoduleXmoduleXpackage.subpackage1.moduleX

一个特殊情况是,如果您以交互方式运行解释器(例如,只需键入并开始动态输入Python代码)。在本例中,该交互式会话的名称为 。python__main__

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

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

相对导入使用模块的 name 属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为“main”),则相对导入会被解析,就好像模块是顶级模块一样,而不管模块在文件系统上的实际位置如何。

相对进口...

相对导入使用模块的名称来确定它在包中的位置。当您使用相对导入(如 )时,这些点表示在包层次结构中增加一些级别。例如,如果当前模块的名称是 ,则表示 。要使 a 正常工作,模块的名称必须至少具有与语句中相同的点数。from .. import foopackage.subpackage1.moduleX..moduleApackage.moduleAfrom .. importimport

...仅在包中是相对的

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

脚本无法导入相对

您可能所做的是尝试从命令行运行或类似操作。执行此操作时,其名称设置为 ,这意味着其中的相对导入将失败,因为它的名称不会显示它位于包中。请注意,如果您从模块所在的同一目录运行Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python会“过早”在当前目录中找到该模块,而没有意识到它是包的一部分。moduleX__main__

另请记住,当您运行交互式解释器时,该交互式会话的“名称”始终为 。因此,您无法直接从交互式会话执行相对导入。相对导入仅用于模块文件。__main__

两种解决方案:

  1. 如果你真的想直接运行,但你仍然希望它被认为是软件包的一部分,你可以做。告诉 Python 将其作为模块加载,而不是作为顶级脚本加载。moduleXpython -m package.subpackage1.moduleX-m

  2. 或者也许你实际上并不想运行,你只是想运行一些其他脚本,比如说,在里面使用函数。如果是这种情况,请放在其他位置而不是在目录中 ) 并运行它。如果你在里面做这样的事情,它会正常工作。moduleXmyfile.pymoduleXmyfile.pypackagemyfile.pyfrom package.moduleA import spam

笔记

  • 对于这两种解决方案中的任何一种,必须可以从 Python 模块搜索路径 () 访问包目录(在您的示例中)。如果不是,您将根本无法可靠地使用包中的任何内容。packagesys.path

  • 从Python 2.6开始,用于包解析目的的模块的“名称”不仅由其属性决定,还由属性决定。这就是为什么我避免使用显式符号来引用模块的“名称”。由于Python 2.6,模块的“名称”是有效的,或者只是如果是。__name____package____name____package__ + '.' + __name____name____package__None


答案 2

这确实是python中的一个问题。混淆的根源在于,人们错误地将相对重要性视为路径相对,而事实并非如此。

例如,当您在 faa.py 中编写以下内容时:

from .. import foo

只有当 faa.py 在执行期间被python识别并加载为包的一部分时,这才有意义。在这种情况下,模块的名称 faa.py 例如为some_packagename.faa。如果文件只是因为它位于当前目录中而加载的,那么当python运行时,它的名称将不会引用任何包,最终相对导入将失败。

在当前目录中引用模块的一个简单解决方案是使用以下方法:

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo