循环导入的最好的解决方法是从架构上优化, 即调整模块和模块成员变量的设计. 一个好的原则是: 可导出的成员变量, 都不应该依赖于导入进来的成员变量.
但是在业务开发的过程中, 总会遇到通过架构层面解决不了的导入问题, 这时候就只能通过语言层面来解决了.
目录结构 (下面的案例的目录结构都是这样的):
root.py
/pack1
__init__.py
module_a.py
/pack2
__init__.py
module_b.py
module_c.py
module_d.py
循环导入例子
首先看一下什么是循环导入和循环导入的原因.
root.py
from pack1.module_a import class_a
module_a.py
- print "start init module a"
- from pack2.module_b import class_b
- class class_a():
- def f(self):
- class_b
- print "init module a"
module_b.py
- print "start init module b"
- from pack1.module_a import class_a
- class class_b():
- def f(self):
- class_a
- print "init module b"
会报错:
- start init module a
- start init module b
- Traceback (most recent call last):
- File "E:/my_demo/demo2016/bѭ/s2/root.py", line 2, in <module>
- from pack1.module_a import class_a
- File "E:\my_demo\demo2016\bѭ\s2\pack1\module_a.py", line 2, in <module>
- from pack2.module_b import class_b
- File "E:\my_demo\demo2016\bѭ\s2\pack2\module_b.py", line 2, in <module>
- from pack1.module_a import class_a
- ImportError: cannot import name class_a
代码执行的流程:
执行 root.py 的
from pack1.module_a import class_a
, 发现需要导入模块 module_a
一个空的字典会被创建, 对应 module_a 的 globals
module_a 的代码会被执行, 当执行到
from pack2.module_b import class_b
时, 发现需要导入模块 module_b
一个空的字典会被创建, 对应 module_b 的 globals
module_b 的代码会被执行, 当执行到
from pack1.module_a import class_a
时, 发现需要导入模块 module_a, 但是此时已经有 module_a 的 globals 了, 所以直接访问字典里的 class_a, 但是由于 module_a 的 globals 还是空的, 即里面没有 class_a, 所以抛出异常
参考文档
所以根本原因是: 在导入的时候, module_b 需要访问 module_a 的变量 class_a, 但是 class_a 没有初始化完成
所以解决方法有两个:
在导入的时候, 让 module_b 不要访问 module_a 的变量, 也就是方案一
class_a 初始化完成后, 才让 module_b 访问 module_a 的变量, 也就是方案二和三
方案一, 使用 import ... 代替 from...import...
root.py
import pack1.module_a
module_a.py
- print "start init module a"
- import pack2.module_b
- class class_a():
- def f(self):
- m_b.class_b
- print "init module a"
- if __name__ == '__main__':
- pass
module_b.py
- print "start init module b"
- import pack1.module_a
- class class_b():
- def f(self):
- pack1.module_a.class_a
- print "init module b"
module_a 和 module_b 都会被编译, 终端会输出:
- start init module a
- start init module b
- init module b
- init module a
即首先编译 a, 编译过程中发现需要编译 b, 编译 b 完成后, 编译 a 剩下的部分,
这个案例不使用 from....import...., 而使用 import, 这样是可以成功循环导入的, 不过一个缺点是, 每次访问 module 的时候, 都需要写全路径, 例如 pack1.module_a.class_a, 非常繁琐.
一个优化的方案是导入的时候, 使用 import....as... 例如: import pack1.module_a as m_a. 但是很奇怪的是, 在 module_a 中可以这样用, 但是在 module_b 中不可以, 否则就会导致报错. 还有如果把 roo.py 改为 import pack2.module_b, 就会反过来, 即 module_b 中可以这样用, 但是在 module_a 中不可以. 所以准确点应该是在 root.py 导入的模块中可以使用, 但是在其他模块不能使用. 所以 import....as... 这个方案并不好.
注意, import... 只能 import 到模块, 不能 import 模块里面的成员变量, 例如 import pack1.module_a.class_a 是不可以的
这个方案的缺点就是访问模块里面的成员变量太繁琐
方案二, 把导入放在后面
root.py
from pack1.module_a import class_a
module_a.py
- print "start init module a"
- #from pack2.module_b import class_b #放在这里会报错
- class class_a():
- def f(self):
- # m_b.class_b
- pass
- from pack2.module_b import class_b #放在这里不会
- class class_c():
- def f(self):
- class_b
- print "init module a"
module_b.py
- print "start init module b"
- from pack1.module_a import class_a
- class class_b():
- def f(self):
- class_a
- print "init module b"
当存在类似的依赖关系: class_c 依赖 class_b 依赖 class_a, 然后 class_a 和 class_c 在同一个模块时, 可以使用这种方案.
把 from pack2.module_b import class_b 这句放在 class_a 后面, 这样在 module_b 中访问 module_a.class_a 是成功的, 因为 class_a 的定义代码已经执行完成, 并被添加到 module_a 的 globals 中.
方案三, 把导入语句放在语句块中
root.py
- from pack1.module_a import func_a
- print 'root start run func a'
- func_a()
- print 'root end run func a'
module_a.py
- print "start init module a"
- def func_a():
- from pack2.module_b import func_b
- func_b()
- print 'run func a'
- print "init module a"
module_b.py
- print "start init module b"
- def func_b():
- from pack1.module_a import func_a
- print 'run func b'
- print "init module b"
输出:
- start init module a
- init module a
- root start run func a
- start init module b
- init module b
- run func b
- run func a
- root end run func a
在需要使用 func_b 的时候, 才进行导入操作, 这样在执行 module_b 的时候, module_a 已经初始化完成, module_a 的 globals 已经有 func_a 了, 所以导入不会报错.
查看已经导入的 module 情况
- import sys
- from pack1.module_a import func_a
- print sys.modules
- # {
- 'pack1': <module 'pack1' from 'E:\my_demo\demo2016\bѭ\s4\pack1\__init__.pyc'>,
- }
- print sys.modules['pack1.module_a'].__dict__
- # {
- 'func_a': <function func_a at 0x0254FB30>, '__doc__': None
- }
- sys.modules['pack1.module_a'].func_a_tmp=sys.modules['pack1.module_a'].func_a
通过 sys.modules 可以访问所有当前已导入的模块.
modules 是一个字典, key 是模块的路径, 例如 pack1.module_a,value 是一个模块对象
模块对象中, 属性名是模块中全局变量的名字, 即 sys.modules['pack1.module_a'].__dict__等于 module_a 里面的 globals()
所以, 当在 module_b 中执行 from pack1.module_a import class_a 时, 相当于执行代码:
- import sys
- if 'pack1.module_a' in sys.modules:
- if hasattr(sys.modules['pack1.module_a'],"class_a"):
- sys.modules['pack2.module_b'].class_a=sys.modules['pack1.module_a'].class_a
- else:
- raise Exception(u"循环导入异常")
- else:
- #执行导入 pack1.module_a 的操作, 也就是初始化一个 module 对象, 然后令 sys.modules['pack1.module_a']= 这个对象
所以解决循环导入的问题, 就相当于使上面的代码不会执行到 raise Exception(u"循环导入异常") 这一句, 方案一和方案二都是通过这种方法解决的.
未经允许, 请不要转载
来源: https://www.cnblogs.com/Xjng/p/10672422.html