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’对象在指定位置没有属性’level2′.

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

但有时候,命名空间设置得“足够好”,导致导入工作.例如,下面的代码,顶层被剥离,总是使用python -c“import level2”,即使我们在从level2.b导入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

编辑

导入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.但是,似乎如果解释器可以在该导入上绑定到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.

最佳答案 我已经针对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和导入文档至少应该对这个极端情况进行解释以及如何通过相对导入来解决它.

点赞