微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

机制 - 为什么直接在 main 上运行并使用相对导入会导致“尝试在没有已知父包的情况下进行相对导入”

如何解决机制 - 为什么直接在 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 属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为“ma​​in”),那么相对导入将被解析为好像该模块是顶级模块,而不管该模块实际位于何处在文件系统上。

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 已经设置,那么导入系统将优先使用它来重新计算 namepath 属性中的包名称。 >

问题

阅读了 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 举报,一经查实,本站将立刻删除。