如何解决机制 - 为什么直接在 main 上运行并使用相对导入会导致“尝试在没有已知父包的情况下进行相对导入”
在 main.py 具有相对导入的地方运行 python main.py
将失败。
<root>
└── src
├── main.py # from main.py,want to use the stuff in other.py
└── other.py
$ python main.py
Traceback (most recent call last):
File "main.py",line 2,in <module>
from . other import use_me
ImportError: attempted relative import with no kNown parent package
- main.py
# 'import ...' is for absolute import only
# For relative import,must be 'from ... import ...'
# See https://www.python.org/dev/peps/pep-0328/#guido-s-decision
from . other import use_me
if __name__ == "__main__":
use_me()
- 其他.py
def use_me():
print("thanks")
PEP 338 -- Executing modules as scripts 很清楚显式相对导入在主模块中不起作用。
2.5b1 的发布显示了此 PEP 和 PEP 328 之间令人惊讶的(尽管回想起来很明显)交互 - 显式相对导入在主模块中不起作用。这是因为相对导入依赖于 __name__
来确定当前模块在包层次结构中的位置。在主模块中,__name__
的值始终为 '__main__'
,因此显式相对导入将始终失败(因为它们仅适用于包内的模块)。 >
原因
原因解释如下,解决方法是更新__path__
。
这个问题实际上并不是 -m 开关所独有的。问题在于相对导入基于 __name__
,并且在主模块中,__name__
始终具有值 __main__
。因此,相对导入当前无法从应用程序的主模块正常工作,因为主模块不知道它真正适合 Python 模块命名空间的位置(对于通过 -m 开关执行的主要模块,这至少在理论上是可以解决的,但是直接执行的文件和交互式解释器完全不走运)。
通过在模块顶部粘贴以下内容(在执行任何相对导入之前),您应该能够获得类似于旧的隐式相对导入行为的内容:这使得相对导入机制将您的主模块视为一个包。这种解决方法的问题在于,就像从主模块隐式相对导入的旧情况一样,您最终可能会在 sys.modules 中得到两个不同的兄弟模块副本。一个副本将是 __main__
.foo' 而另一个将是 'package.foo'(对于隐式相对导入,第一个副本将是一个名为 'foo' 的顶级模块)。
if __name__ = '__main__':
from os.path import dirname
__path__ = [dirname(__file__)]
del dirname
相对导入和__name__
相对导入使用模块的 name 属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为“main”),那么相对导入将被解析为好像该模块是顶级模块,而不管该模块实际位于何处在文件系统上。
PEP 366 似乎启用了将 main.py 作为模块执行的解决方法。
main.py 可以通过以下方式执行:python -m <parent_directory_name>.main
。
这个 PEP 提出了一种向后兼容的机制,允许使用来自包内可执行模块的显式相对导入。由于 PEP 328 和 PEP 338 之间的尴尬交互,此类导入目前失败。
通过添加新的模块级别属性,如果使用 -m 开关执行模块,则此 PEP 允许相对导入自动工作。当文件按名称执行时,模块本身中的少量样板将允许相关导入工作。
主要提议的更改是引入了新的模块级属性 package。当它存在时,相对导入将基于此属性而不是模块 name 属性。
与当前的 name 属性一样,设置 package 将由用于导入模块的 PEP 302 加载器负责。使用 imp.new_module() 创建模块对象的加载器会将新属性自动设置为 None。当导入系统在没有设置package(或设置为None)的模块中遇到显式相对导入时,它将计算并存储正确的值(name.rpartition ('.')[0] 用于普通模块,name 用于包初始化模块)。如果 package 已经设置,那么导入系统将优先使用它来重新计算 name 和 path 属性中的包名称。 >
问题
阅读了 1510172 和 PEP 366,仍然无法理解错误发生的确切机制。
我想有一个使用 __name__
的模块加载器,它看起来代表模块层次结构。在下面的文件夹层次结构中,它看起来以 <root>
开头,以模块名称本身结尾,但对于由 __main__
运行的主模块,它以 python <script>.py
结尾。
<root>
└── src
├── main.py # from main.py,want to use the stuff in other.py
└── other.py
这些__name__
、__path__
、__package__
、模块加载器是如何相关的,以及
幕后到底发生了什么以及它是如何导致问题的?
解决方法
在 Relative imports in Python 3 中找到答案。
注意:issue 18018的补丁增加了一个if块,会在上面的代码之前执行:
if (PyUnicode_CompareWithASCIIString(package,"") == 0) {
PyErr_SetString(PyExc_ImportError,"attempted relative import with no known parent package");
goto error;
}
现在在 cpython/blob/master/Python/import.c。
last_dot = PyUnicode_GET_LENGTH(package);
if (last_dot == 0) {
goto no_parent_error;
}
所以我的理解是,如果 __name__
设置为没有点的 '__main__'
,它只会转到 "attempted relative import with no known parent package"
。
答案还说明了:
我们可以使用 -m 命令行选项运行该模块,该选项将“搜索命名模块的 sys.path 并将其内容作为 __main__
模块*”执行
...
-m
为您完成所有导入工作并自动设置 __package__
,但您可以自己完成
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。