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

问题描述

python main.py在 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
# '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()
def use_me():
    print("thanks") 

PEP 338 - 将模块作为脚本执行很明显,显式相对导入在主模块中不起作用

2.5b1 的发布显示了此 PEP 和 PEP 328 之间令人惊讶的(尽管回想起来很明显)交互 -显式相对导入在主模块中不起作用。这是因为相对导入依赖于__name__确定当前模块在包层次结构中的位置。在主模块中,值__name__always '__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__

相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为' main '),那么无论模块实际位于文件系统上的哪个位置,都将相对导入解析为就好像该模块是顶级模块一样。

PEP 366 似乎启用了将 main.py 作为模块执行的解决方法。

main.py 可以通过以下方式执行:python -m <parent_directory_name>.main.

这个 PEP 提出了一种向后兼容的机制,允许使用来自包内可执行模块的显式相对导入。由于 PEP 328 和 PEP 338 之间的尴尬交互,此类导入当前失败。

通过添加新的模块级别属性,如果使用 -m 开关执行模块,此 PEP 允许相对导入自动工作。当文件按名称执行时,模块本身的少量样板将允许相对导入工作。

提议的主要更改是引入了一个新的模块级属性package。当它存在时,相对导入将基于此属性而不是模块名称属性。

与当前名称属性一样,设置将由用于导入模块的 PEP 302 加载器负责。使用 imp.new_module() 创建模块对象的加载器将自动将新属性设置为无。当导入系统在没有设置包(或设置为无)的模块中遇到显式相对导入时,它将计算并存储正确的值(name .rpartition('.')[0] 用于普通模块和名称用于包初始化模块)。如果已经设置了包,那么导入系统将优先使用它,而不是根据名称路径属性重新计算包名称。

问题

阅读 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__, 模块加载器是如何相关的,幕后到底发生了什么以及它是如何导致问题的?

标签: pythonpython-modulerelative-import

解决方案


在 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__,但您可以这样做那你自己


推荐阅读