我如何修复ImportError:尝试相对于顶级包以外的相对导入错误。

23 浏览
0 Comments

我如何修复ImportError:尝试相对于顶级包以外的相对导入错误。

我来过这些地方:

还有很多我没复制的网址,有些在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会给出这个错误消息,它所说的“非包”是什么意思,为什么以及如何定义一个“包”,以及用易于幼儿理解的术语给出精确答案。

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

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

0
0 Comments

脚本 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__。因此你不能直接从交互式会话进行相对导入。相对导入仅适用于模块文件内部。

两种解决方法:

  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