【问题标题】:Rules for when recursive package imports fail递归包导入失败时的规则
【发布时间】:2015-12-27 19:02:27
【问题描述】:

这是在今天回答 another question 时出现的。

假设以下文件,其中 cmets 表示文件名:

# level1/__init__.py
    import level1.level2
    answer = level1.level2.answer

# level1/level2/__init__.py
    from .b import answer

# level1/level2/b.py
    from .a import answer
    from ..level2.a import answer
    from level1.level2.a import answer
    import level1.level2.a

    if answer != 42:
        answer = level1.level2.a.answer  # <-- Fails here

#level1/level2/a.py
    answer = 42

使用此代码,python -c "import level1" 可以正常工作(在 2.7 和 3.4 中)。但是将答案更改为 42 以外的任何内容,它都会在指定位置出现 AttributeError: 'module' object has no attribute 'level2' 失败。

似乎 from/import 可以在所有父命名空间设置之前导入包的子模块并将变量拉出其命名空间,但是(显然)必须在 sub 的属性之前设置父命名空间-命名空间可以通过普通的属性访问来遍历。

不过,有时,命名空间设置得“足够好”以供导入工作。例如,下面的代码,去掉了顶层,总是与python -c "import level2"一起工作,即使当我们从level2.bimport level2.a时我们还没有完成初始化level2命名空间。

# level2/__init__.py
    from .b import answer

# level2/b.py
    from .a import answer
    import level2.a

    if answer != 42:
        answer = level2.a.answer  # <-- Works here

#level1/level2/a.py
    answer = 41

编辑

import x.y.z 似乎会将对 z 的引用插入到 y 中,但不会将对 y 的引用插入到 x 中。例如,当我从第一个示例中更改 level1/level2/b.py 时:

# level1/level2/b.py
    from sys import modules

    def dump():
        print '\n Dumping...'
        for key, value in sorted(modules.items()):
            if key.startswith('level'):
                print key, [ x for x in dir(value) if not x.startswith('_')]

    dump()
    import level1.level2.a
    dump()

    from .a import answer
    from ..level2.a import answer
    from level1.level2.a import answer

    if answer != 42:
        answer = level1.level2.a.answer

我在回溯之前得到以下结果:

 Dumping...
level1 []
level1.level2 []
level1.level2.b ['dump', 'modules']
level1.level2.sys []

 Dumping...
level1 []
level1.level2 ['a']
level1.level2.a ['answer']
level1.level2.b ['dump', 'level1', 'modules']
level1.level2.level1 []
level1.level2.sys []

但是,如果在第二次调用 dump() 之后,我添加了以下行:

setattr(modules['level1'], 'level2', modules['level1.level2'])

那么它不会失败,因为在通过属性查找访问它之前,我已经将level2 绑定到level1。但是,如果解释器可以在该导入时将a 绑定到level1,它可能也可以以完全相同的方式将level2 绑定到level

在 import 语句期间只更新最低级别的包是否有充分的理由,或者它是一个错误(或者更确切地说,可能是为单个包添加的一个特性,应该扩展到嵌套包)?

注意根据 Python 文档,When a submodule is loaded using any mechanism ... a binding is placed in the parent module’s namespace to the submodule object.

我相信这会发生。但在所有情况下都不够快。此外,它的文档可能是relatively new

【问题讨论】:

  • 您能否更具体地说明“在指定位置出现导入错误失败”?我知道它在import 期间失败了(这就是我们正在做的一切),但回溯说AttributeError: 'module' object has no attribute 'level2',这是真的:它没有。我猜“当你导入父级时,你不能自动访问子”是我简单的英文解释。在后者中,您正在访问一个模块,而不是试图进入另一个包。我可以参考语法更正式地写出来,但我觉得我错过了一些东西,所以先评论......
  • @JRichardSnape -- 对不起,你说得对。正在考虑 AttributeError 并编写 ImportError。现在逐字复制。 但是... 这并没有直接向我解释为什么我在第二个示例中没有遇到同样的问题——在这种情况下,为什么a 可以从level2 中退出在我们完成构建level2 之前? FWIW,我认为这不能用语法来解释——我认为这两种情况基本相同——但我当然可能是错的。
  • 嗯 - 我会想更多。我假设你已经通过了语法(并且,作为一个推论,我错过了一些东西)并且你现在已经按照我接下来要测试的思路编辑了一些不错的调试结果,所以需要弄清楚我的想法围绕他们。
  • @JRichardSnape -- 还有一件事。如果在第二次转储之后,我添加了setattr(modules['level1'], 'level2', modules['level1.level2']) 行,那么它工作正常。这似乎是 import 语句可以自行设置的东西。
  • 有趣。当它工作时,在整个事情之后做一个dump()(即answer == 42)。也很有趣……

标签: python


【解决方案1】:

我已针对 Python 提交了an issue。本期正文转载如下:

PEP 8 建议绝对导入而不是相对导入,导入文档的第 5.4.2 节说导入将导致绑定放置在导入模块的父级命名空间中。

但是,由于(对于所有当前的 Python 版本)此绑定直到模块主体执行之后才进行,因此在某些情况下,相对导入可以正常工作,但绝对导入会失败。考虑这五个文件的简单情况:

xyz.py: import x
x/__init__.py:  import x.y
x/y/__init__.py:  import x.y.a
x/y/a/__init__.py:  import x.y.b; foo = x.y.b.foo
x/y/b/__init__.py:  foo = 1

这将以一种令外行人非常惊讶的方式失败。它不会在任何导入语句上失败;相反,它会在 x.y.a 中的赋值语句上出现 AttributeError 失败,因为 y 的导入尚未完成,因此 y 尚未绑定到 x。

可以想象,这可以通过在执行 exec 之前执行绑定在导入机制中修复。是否可以干净利落地完成,以免造成与现有加载器的兼容性问题,是核心维护者的问题。

但是,如果确定当前行为是可以接受的,那么 PEP 8 和导入文档至少都应该解释这种极端情况以及如何通过相对导入来解决它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-08
    • 2019-12-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多