交叉引用Python模块造成的Bug

Last Updated: 2023-04-23 10:02:40 Sunday

-- TOC --

有错误的测试代码

$ cat a.py
from b import d
class c: pass

$ cat b.py
from a import c
class d: pass

错误表现

$ python3 a.py
Traceback (most recent call last):
  File "a.py", line 1, in 
    from b import d
  File "/home/xinlin/test/b.py", line 1, in <module>
    from a import c
  File "/home/xinlin/test/a.py", line 1, in <module>
    from b import d
ImportError: cannot import name 'd' from 'b' (/home/xinlin/test/b.py)
$ python3 b.py
Traceback (most recent call last):
  File "b.py", line 1, in 
    from a import c
  File "/home/xinlin/test/a.py", line 1, in <module>
    from b import d
  File "/home/xinlin/test/b.py", line 1, in <module>
    from a import c
ImportError: cannot import name 'c' from 'a' (/home/xinlin/test/a.py)

错误原因分析

  1. 执行a.py中的from b import d,由于是执行的python a.py,此时在sys.modules中并没有module b存在,python解释器首先为b.py在sys.modules中创建一个module对象,注意,这时创建的这个module对象是空的,里边啥也没有。在Python内部创建了这个module对象之后,就会解析执行b.py,其目的是填充module b这个dict。
  2. 在执行b.py的过程中,首先会碰到这一句from a import c,此时检查sys.modules这个module缓存中是否已经存在module a了,类似的,Python内部会为a.py创建一个module对象,然后,同样地,执行a.py中的语句。
  3. 再次执行a.py中的from b import d,这时,由于在第1步时,创建的module b对象已经缓存在了sys.modules中,所以直接就得到了module b,但是请注意,从整个过程来看,我们知道,这时module b还是一个空的对象,里面啥也没有,所以从这个module中获得符号d的操作就会抛出异常。

修改方式

不要使用from ... import ...,直接import。

注意

每个模块,在Python解释器中,都只会真正地import一次,每个模块在内存中,只有一份!

本文链接:https://cs.pynote.net/sf/python/202301242/

-- EOF --

-- MORE --