在循环引用下理解python的import * mechanics

时间:2015-02-16 22:29:41

标签: python

我正在玩示例以回答question posted here on SO并且发现很难理解python的import *弄乱范围的机制。

首先是一些背景:这个问题不涉及实际问题;我很清楚from foo import *是不赞成的(这是正确的)并且我认为它是的原因比代码中的清晰度更深。我的兴趣在于通过循环import * s 来理解导致不良行为的机制。换句话说,我理解 预期观察到的行为;我不明白为什么

我无法理解的情况是使用{从导入的模块(b)引用导入模块(a)时出现的问题{1}}。当导入模块使用*时,我设法观察到行为的细微差别,但整体(坏)行为是相同的。我既没有在文件中也没有在SO上找到任何明确的解释。

通过范围内可用的内容调查行为,我设法构建了一个小例子,根据上面提到的问题以及我在SO和其他地方进行的一些搜索来说明其内容的差异。我试着尽可能简明扼要地展示。下面的所有代码和实验都是使用python 2.7.8完成的。


工作方案

首先是一个包含一个包含一个类*

的普通模块的普通模块
a.py

客户端代码的第一个变体,导入模块a,class A: pass

b_v1.py

相同代码的第二个变体,从模块from pprint import pprint def dump_frame(offset=0): import sys frame = sys._getframe(1+offset) d = frame.f_globals d.update(frame.f_locals) return d print 'before import v1' pprint (dump_frame()) import a print 'after import v1' pprint (dump_frame()) print a.A() 导入*a

b_v2.py
  • 在导入之前运行from pprint import pprint def dump_frame(offset=0): import sys frame = sys._getframe(1+offset) d = frame.f_globals d.update(frame.f_locals) return d print 'before import v2' pprint (dump_frame()) from a import * print 'after import v2' pprint (dump_frame()) print A() b_v1会产生相同的输出,并且两者都能够按预期实例化A。然而,在导入之后,正如预期的那样,它们会有所不同。我强调了区别:

b_v2,范围

b_v1.py

'a': <module 'a' from '.../stackoverflow/instance/a.py'> 没有,但有

b_v2.py
  • 导入前后,范围包含'A': <class a.A at 0x...> 设置为__builtins__

  • 两种变体都成功实例化<module '__builtin__' (built-in)>


不工作的情况

有趣的行为是将A更改为包含a.py 的循环引用(在bb_v1变体中)。

调整后的b_v2代码:

a.py

(为了简洁起见,只显示了from b_v1 import * class A: pass 的一个案例;显然在a.py的情况下导入的是此模块,而不是b_v2.py

在我对带有循环引用的场景中范围内容的观察中,我看到:

  • 在两种变体中,b_v1.py中导入之前,a与上述情况类似。但是,导入后,它会被更改并包含__builtins__

    &#39;算术错误&#39;: &#39; AssertionError&#39;:, &#39; AttributeError&#39; :, ...

这是不必要的长时间粘贴在这里。

  • 更改的dict出现两次。我可以理解这是导入的结果,如果代码在函数内部,可能不会发生。
  • 在变体__builtins__中,模块b_v2出现在范围内;它存在于变体a中。

  • 在两种变体中,b_v1的实例化都失败了。鉴于变量A中的模块存在于范围内(因此,我假设已成功导入),我原本希望能够实例化b_v1。不是这种情况。但是有一些区别:如果A,则b_v1.py失败,AttributeError: 'module' object has no attribute 'A'失败,失败为b_v2.py。在后一种情况下,它始终是相同的错误,与我是否尝试实例化为NameError的{​​{1}}(如工作示例中)无关。


总结我的问题:

  • 通过什么机制,广告A()会弄乱范围

  • 为什么在b_v1的情况下无法实例化a.A(),尽管模块在范围内?

1 个答案:

答案 0 :(得分:4)

Python模块从上到下执行。 Import语句可以像任何其他语句一样执行。当运行import语句时,它会执行这些操作(出于说明目的简化,有关完整详细信息,请参阅language reference):

  1. 检查模块是否列在sys.modules中。如果是,立即归还
  2. 找到模块(通常但不总是通过搜索文件系统)。
  3. sys.modules中为模块创建一个空条目,并带有一个空命名空间。
  4. 在新创建的命名空间中从上到下执行模块。
  5. 假设我们有这样的文件:

    a.py

    from b import *
    foo = object()
    

    b.py

    from a import *
    print(repr(foo))
    

    进一步假设首先导入a.py。让我们逐行完成:

    1. 其他人导入a。在我们开始执行a之前,sys.modules['a']的引用存储在a.py中。
    2. from b import *运行import b。这转换为“b,然后从a的命名空间中取出所有内容到sys.modules['b']的命名空间。”
    3. Python在b.py
    4. 中放置一个空模块对象
    5. from a import *运行a。 Python导入a
    6. sys.modules['a']存在以来,a.py的导入会立即返回。
    7. 由于foo = object()尚未执行a.foob尚不存在,因此无法将其转储到b.py的命名空间中。
    8. NameError在{{1}}上崩溃。
相关问题