相对导入,又一次解释
相对导入,又一次解释
我在这里:
- http://www.python.org/dev/peps/pep-0328/
- http://docs.python.org/2/tutorial/modules.html#packages
- Python packages: relative imports
- python relative import example code does not work
- Relative imports in python 2.5
- Relative imports in Python
- Python: Disabling relative import
还有许多我没有复制的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为什么会给出这个错误消息,它所说的“非包”是什么意思,为什么以及如何定义一个“包”,并给出容易被幼儿理解的确切答案。
这实际上是 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
脚本与模块
以下是解释。简短版本是,直接运行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 moduleX
,moduleX
的名称将只是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__
。因此,你无法直接从交互式会话中进行相对导入。相对导入只适用于模块文件内部。两种解决方案:
如果您真的想直接运行
moduleX
,但仍希望它被视为包的一部分,则可以执行python -m package.subpackage1.moduleX
。-m
告诉Python将其作为模块而不是作为顶级脚本加载。或者,也许您实际上不想运行
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__。