相对导入,又一次解释

45 浏览
0 Comments

相对导入,又一次解释

我在这里:

还有许多我没有复制的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中有这样的回复:

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

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

有人可以告诉我Python为什么会给出这个错误消息,它所说的“非包”是什么意思,为什么以及如何定义一个“包”,并给出容易被幼儿理解的确切答案。

admin 更改状态以发布 2023年5月22日
0
0 Comments

这实际上是 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

0
0 Comments

脚本与模块

以下是解释。简短版本是,直接运行Python文件和从其他地方导入文件之间有很大的区别。只知道文件所在的目录并不能确定Python认为它在哪个包中。这还取决于你如何将文件加载到Python中(通过运行或导入)。

有两种加载Python文件的方式:作为顶层脚本或作为模块。如果您直接执行它,例如在命令行上键入python myfile.py,则文件将作为顶层脚本加载。当在某个其他文件中遇到import语句时,它将作为模块加载。一次只能有一个顶层脚本;顶层脚本是您运行的Python文件,用来启动整个程序。

命名

当一个文件被加载时,它会被赋予一个名称(存储在它的__name__属性中)。

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

但是要注意,如果你使用像python -m package.subpackage1.moduleX这样的命令从shell命令行中加载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”),则相对导入将被解析为顶层模块,而不管模块实际上位于文件系统上的位置。

访问不通过包访问模块

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

有一个特殊情况是,如果你以交互方式运行解释器(例如,只需键入python并开始实时输入Python代码),那么该交互会话的名称是__main__

现在这里是你错误消息的关键所在:如果模块的名称中没有点,则认为它不是包的一部分。它实际上在磁盘上的位置无关紧要。所有的都取决于它的名称,而它的名称取决于你如何加载它。

现在看看你在问题中引用的引用:

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

相对导入...

相对导入使用模块的名称来确定它在包中的位置。当你使用类似于from .. import foo的相对导入时,点表示在包层次结构中向上移动若干级别。例如,如果你当前的模块名称是package.subpackage1.moduleX,那么..moduleA表示package.moduleA。为了使from .. import工作,模块的名称必须至少具有与import语句中的点数相同的点数。

...只有在包中才是相对的

但是,如果你的模块名称是__main__,则认

脚本无法使用相对导入

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

此外,请记住当你运行交互式解释器时,该交互会话的“名称”始终为__main__。因此,你无法直接从交互式会话中进行相对导入。相对导入只适用于模块文件内部。

两种解决方案:

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

  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,如果__package__为None,则只是__name__。

0