PyCharm 在相对导入中显示错误,即使它不是错误。

19 浏览
0 Comments

PyCharm 在相对导入中显示错误,即使它不是错误。

我已经访问过以下网站:

还有很多我没有复制的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中,但对我来说这一切都是外人。我访问的其中一个网址上有这样的回答:

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

上面的回复看起来很有前途,但对我来说,这全是象形文字。那么我的问题是,如何使Python不将“Attempted relative import in non-package”返回给我,它的答案涉及-m,据说是这样。

有人能告诉我为什么Python会出现这个错误消息,它所谓的“非包”是什么意思,为什么和如何定义一个\'包\',以及用幼儿易懂的术语准确回答这个问题。

admin 更改状态以发布 2023年5月24日
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

脚本 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语句。如果你试图这样做,你将得到“在非包中使用相对导入”错误。

脚本无法导入相对路径

你可能尝试从命令行中运行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的事情,它将可以正常工作。


注意事项

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

  • 自Python 2.6以来,模块的用于解决包的“名称”不仅由其 __name__ 属性确定,而且还由 __package__ 属性确定。 这就是为什么我想避免使用显式符号 __name__ 引用模块的“名称”。 自Python 2.6以来,模块的“名称”有效地为 __package__ + '.' + __name__ ,如果 __package__ None ,则仅为 __name__ 。)

0