代码之家  ›  专栏  ›  技术社区  ›  Błażej Michalik saloni

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

  •  5
  • Błażej Michalik saloni  · 技术社区  · 4 年前

    我注意到了 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)
    

    不是模块
    <来自“C:\Users\MrM\Destdesk\testpy\test.py”的模块“testpy.test”>

    这意味着 test 使用星号导入后,变量被阴影覆盖。

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

    在没有赋值的情况下,从非指定文件运行上述示例 __init__.py 将提高a NameError .

    这种行为从何而来?进口系统的规范中是否概述了这一点?背后的原因是什么 __init__.py 必须以这种方式变得特别吗?不是 in the reference 或者至少我找不到它。

    0 回复  |  直到 4 年前
        1
  •  8
  •   tdelaney    4 年前

    此行为在中定义 The import system 文件部分 5.4.2 Submodules

    当使用任何机制(例如importlib API, 导入或从语句导入,或内置 进口 一 绑定被放置在子模块的父模块命名空间中 对象。例如,如果垃圾包有一个子模块foo,在 导入spam.foo后,spam将有一个属性foo,该属性绑定到 子模块。

    包命名空间包括在中创建的命名空间 __init__.py 加上导入系统添加的额外功能。这个 为什么? 用于命名空间一致性。

    考虑到Python熟悉的名称绑定规则,这似乎令人惊讶, 但这实际上是进口体系的一个基本特征。这个 不变保持是指如果你有sys.modules['spam']和 sys.modules['spam.foo'](正如您在上述导入后所做的那样) 后者必须显示为前者的foo属性。

        2
  •  0
  •   metatoaster    4 年前

    这似乎与解释器如何在模块/子模块级别解析变量赋值的相互作用有关。如果我们使用在我们试图查询的模块之外执行的代码来查询任务,我们可能能够获取更多信息。

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

    代码列表 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
    

    新功能将测试是否 模块 实际上被分配到以下范围 __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
    

    事实上,事实并非如此,因此解释器确实在解析引用 模块 通过导入系统,而不是分配给范围内的任何变量 __init__.py 因此,先前的直觉实际上是错误的,但实际上是解释者解决了 模块 名称内 example.package (即使这是在 __init__.py )通过模块系统一次 example.package.module 是进口的。

    我还没有研究过处理模块和导入的赋值/名称解析的具体PEP,但考虑到这个小练习证明了这个问题不仅仅依赖于相对导入,而且无论何时何地完成导入,赋值都会被触发,因此可能存在一些问题,但这希望能让我们更好地理解Python的导入系统如何处理与导入模块相关的名称解析。