zoukankan      html  css  js  c++  java
  • Python的循环导入问题

    循环导入的最好的解决方法是从架构上优化,即调整模块和模块成员变量的设计。一个好的原则是:可导出的成员变量,都不应该依赖于导入进来的成员变量。
    但是在业务开发的过程中,总会遇到通过架构层面解决不了的导入问题,这时候就只能通过语言层面来解决了。

    目录结构(下面的案例的目录结构都是这样的):

    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_demodemo2016ѭ������s2pack1module_a.py", line 2, in <module>
        from pack2.module_b import class_b
      File "E:my_demodemo2016ѭ������s2pack2module_b.py", line 2, in <module>
        from pack1.module_a import class_a
    ImportError: cannot import name class_a
    

    代码执行的流程:

    1. 执行root.py的from pack1.module_a import class_a,发现需要导入模块module_a
    2. 一个空的字典会被创建,对应module_a的globals
    3. module_a的代码会被执行,当执行到from pack2.module_b import class_b时,发现需要导入模块module_b
    4. 一个空的字典会被创建,对应module_b的globals
    5. 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没有初始化完成
    所以解决方法有两个:

    1. 在导入的时候,让module_b不要访问module_a的变量,也就是方案一
    2. 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_demodemo2016ѭ������s4pack1\__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"循环导入异常")这一句,方案一和方案二都是通过这种方法解决的。

    未经允许,请不要转载

  • 相关阅读:
    aop日志记录
    RocketMQ 启动停止命令
    windows搭建RocketMQ服务
    zTree实战
    springboot 传List参数
    Spring Boot之 Controller 接收参数和返回数据总结(包括上传、下载文件)
    SpringBoot Controller接收参数的几种常用方
    clob大数据转换为多行数据
    oracle dba学习
    TreeNode(包含读出文件里的信息)
  • 原文地址:https://www.cnblogs.com/Xjng/p/10672422.html
Copyright © 2011-2022 走看看