递归包导入失败的规则

时间:2015-09-30 05:37:07

标签: python

这是在今天回答another question的背景下提出的。

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

# 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的属性之前设置父命名空间-namespaces可以通过普通的属性访问来遍历。

但是,有时候,命名空间已经设置好了#34;#34;为了进口工作。例如,下面的代码,顶层被剥离,始终与python -c "import level2"一起使用,即使我们level2来自import level2.a时仍未完成初始化level2.b命名空间{1}}。

# 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中完全相同方式。

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

注意根据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

1 个答案:

答案 0 :(得分:2)

我已经针对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。

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

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