本地范围与 __init__.py 内的相对导入

Local scope vs relative imports inside __init__.py

我注意到 asyncio/init.py from python 3.6 使用了以下结构:

from .base_events import *

...

__all__ = (base_events.__all__ + ...)

base_events 符号没有在源代码中的任何地方导入,但模块仍然包含它的局部变量。

我已经用下面的代码检查了这个行为,放入一个 __init__.py 旁边有一个虚拟 test.py

test = "not a module"
print(test)

from .test import *
print(test)

not a module
<module 'testpy.test' from 'C:\Users\MrM\Desktop\testpy\test.py'>

这意味着 test 变量在使用星号导入后被隐藏了。

我摆弄了一下,发现它不一定是明星进口,但它必须在__init__.py内,而且必须是相对的。否则模块对象不会被分配到任何地方。

如果没有分配,运行 上面的示例来自一个不是 __init__.py 的文件将引发 NameError.

这种行为从何而来?这在某处的导入系统规范中有概述吗? __init__.py 必须以这种方式特别的原因是什么?不是in the reference,或者至少我找不到。

这似乎与解释器如何将变量赋值解析为 module/submodule 级别的相互作用有关。如果我们改为询问使用在我们试图询问的模块之外执行的代码的分配,我们可能能够获取更多信息。

在我的示例中,我有以下内容:

src/example/package/module.py 的代码清单:

from logging import getLogger
__all__ = ['fn1']
logger = getLogger(__name__)

def fn1():
    logger.warning('running fn1')
    return 'fn1'

src/example/package/__init__.py 的代码清单:

def print_module():
    print("`module` is assigned with %r" % module)

现在在交互式解释器中执行以下命令:

>>> from example.package import print_module
>>> print_module()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/example.package/src/example/package/__init__.py", line 2, in print_module
    print("`module` is assigned with %r" % module)
NameError: name 'module' is not defined

到目前为止一切顺利,异常看起来完全正常。现在让我们看看如果 example.package.module 被导入会发生什么:

>>> import example.package.module
>>> print_module()
`module` is assigned with <module 'example.package.module' from '/tmp/example.package/src/example/package/module.py'>

鉴于相对导入是完整导入的简写语法,让我们看看如果我们将 __init__.py 修改为包含绝对导入而不是像刚刚在交互式解释器中所做的相对导入会发生什么看看现在会发生什么:

import example.package.module
def print_module():
    print("`module` is assigned with %r" % module)

再次启动交互式解释器,我们看到:

>>> print_module()
`module` is assigned with <module 'example.package.module' from '/tmp/example.package/src/example/package/module.py'>

注意 __init__.py 实际上代表模块绑定 example.package,直觉可能是如果导入 example.package.module,解释器将提供 module 的赋值至 example.package 以帮助解决 example.package.module,而不管进行的是绝对导入还是相对导入。这似乎是在可能具有子模块(即__init__.py)的模块中执行代码的一个特殊怪癖。

其实,再考一次。让我们看看变量赋值是否有什么奇怪的地方。修改src/example/package/__init__.py为:

import example.package.module

def print_module():
    print("`module` is assigned with %r" % module)

def delete_module():
    del module

新函数将测试 module 是否实际分配给 __init__.py 处的作用域。执行此操作我们了解到:


>>> from example.package import print_module, delete_module
>>> print_module()
`module` is assigned with <module 'example.package.module' from '/tmp/example.package/src/example/package/module.py'>
>>> delete_module()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/example.package/src/example/package/__init__.py", line 7, in delete_module
    del module
UnboundLocalError: local variable 'module' referenced before assignment

事实上,它不是,所以解释器真正通过导入系统解析 module 处的引用,而不是分配给 __init__.py 范围内的任何变量。所以先前的直觉实际上是错误的,而是解释器在 example.package 中解析 module 名称(即使这是在 __init__.py 的范围内完成)一次 example.package.module 已导入。

我还没有查看处理 assignment/name 模块和导入解决方案的特定 PEP,但考虑到这个小练习证明问题不仅仅依赖于相对导入,而且分配是不管导入是在何时何地完成的,都可能会触发,但希望这能更好地理解 Python 的导入系统如何处理与导入模块相关的解析名称。

此行为在 The import system documentation section 5.4.2 Submodules

中定义

When a submodule is loaded using any mechanism (e.g. importlib APIs, the import or import-from statements, or built-in import()) a binding is placed in the parent module’s namespace to the submodule object. For example, if package spam has a submodule foo, after importing spam.foo, spam will have an attribute foo which is bound to the submodule.

包命名空间包括在 __init__.py 中创建的命名空间加上导入系统添加的额外内容。 为什么 是为了命名空间的一致性。

Given Python’s familiar name binding rules this might seem surprising, but it’s actually a fundamental feature of the import system. The invariant holding is that if you have sys.modules['spam'] and sys.modules['spam.foo'] (as you would after the above import), the latter must appear as the foo attribute of the former.