Python的循环导入问题_Python 学习者的博客-程序员宅基地_python循环导入问题

技术标签: 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

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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

代码执行的流程:

  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

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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情况

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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"循环导入异常")这一句,方案一和方案二都是通过这种方法解决的。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_38682860/article/details/108622292

智能推荐

php输出txt文件乱码,php生成txt乱码_weixin_39639381的博客-程序员宅基地

前言相信很多人在使用PHP的过程中都遇到过生成文件乱码的情况,不论是使用fwrite还是file_put_contents写入。可能你会先尝试从编码入手尝试解决,但最终的结果往往是不理想的,尽管我们都将其转换为了UTF-8编码了...那么究其根本原因是什么呢?一句话:缺少头部BOM(当然,这里指的肯定不是Js的Bom)BOM既然提到了BOM,那么可能有的同学不太了解这个家伙,这里我简单说一下,老鸟...

linq to xml操作XML_weixin_34018169的博客-程序员宅基地

linq to xml操作XML LINQ to XML提供了更方便的读写xml方式。前几篇文章的评论中总有朋友提,你为啥不用linq to xml?现在到时候了,linq to xml出场了。.Net中的System.Xml.Linq命名空间提供了linq to xml的支持。这个命名空间中的XDocument,XElement以及XText,XAttribute提供了读...

适配android7.0 miui,Android调用相机、相册、裁剪(适配7.0和MIUI系统)_pelican最小的博客-程序员宅基地

实现设置头像功能的具体步骤创建实现功能的视图界面设置权限(android 6.0以上需要动态申请)选择拍照或者相册选择图片选择图片后的进行裁剪(看需求)裁剪完后对结果进行处理(设置到界面、上传到服务器等)最后会贴上整个测试类的代码1、创建实现功能的视图界面类似如下所示,具体内容根据项目需求去实现设置头像.png设置方式.png2、设置权限(6.0以上需要动态申请)首先在AndroidManifes...

小波包分解_苏学算法的博客-程序员宅基地_小波包分解

参考:小波与小波包、小波包分解与信号重构、小波包能量特征提取 暨 小波包分解后实现按频率大小分布重新排列(Matlab 程序详解)

基于MATLAB的BP神经网络手写数字识别_matlab汪汪队的博客-程序员宅基地

在信息化飞速发展的时代,光学字符识别是一个重要的信息录入与信息转化的手段,其中手写体数字的识别有着广泛地应用,如:邮政编码、统计报表、银行票据等等,因其广泛地应用范围,能带来巨大的经济与社会效益。本文结合深度学习理论,利用BP神经网络对手写体数字数据集MNIST进行分析,作为机器学习课程的一次实践,熟悉了目前广泛使用的Matlab工具,深入理解了神经网络的训练过程,作为非计算机专业的学生,结合该课...

python符号运算库_SymEngine:一个快速的C++符号数学运算库_weixin_39928106的博客-程序员宅基地

SymEngine SymEngine is a standalone fast C++ symbolic manipulation library. Optional thin wrappers allow usage of the library from other languages, e.g.:C wrappers allow usage from C, or as a basis ...

随便推点

加密解密_weixin_30348519的博客-程序员宅基地

2013年9月23日转: Generating a Key from a Password.NET Security Bloghttp://blogs.msdn.com/b/shawnfa/archive/2004/04/14/generating-a-key-from-a-password.aspxIf you're trying to encrypt data usin...

经典重译 | 来自华盛顿大学教授的机器学习“内功心法”_weixin_34361881的博客-程序员宅基地

本文最初发表在 《ACM通讯》2012年第10辑。作者是华盛顿大学教授,著名的机器学习专家 Pedro Domingos。作者2016年凭借畅销书《终极算法》而名声大噪,成为全球机器学习领域代表人物之一。而5年前 Domingos 教授发表的这篇“内功心法”,也是整个国外机器学习领域几乎人人必读的一篇文章。在很多大学的机器学习课程以及优达学城等在线培训体系中,都推荐学生精读此文。这篇文章高屋建瓴的...

七牛上传图片第一次成功后面不成功_huang_yongya的博客-程序员宅基地

记录一下做七牛上传图片遇到的一个问题。上传图片,用的七牛sdk上传的,调用的是系统的相册,但是有一个问题,在配置好空间以及token后,第一次上传是成功的,但是后面再次上传到这个空间后,就会上传失败。最后找到原因了,[upManager putData:data key:userId token:token complete:^(QNResponseInfo *info, NSSt

【Latex】从零开始学论文排版软件_孑渡的博客-程序员宅基地

从零开始学LaTex排版软件!跟着博主小白一起,从零基础开始学会使用LaTex,告别烦恼的Word排版~

linux 内核空间 sy,在 Linux 下用户空间与内核空间数据交换的方式,第 1 部分: 内核启动参数、模块参数与sysf..._weixin_39633113的博客-程序员宅基地

级别: 初级燚 杨 (), 计算机科学硕士2006 年 2 月 16 日本系列文章包括两篇,它们文详细地介绍了 Linux 系统下用户空间与内核空间数据交换的九种方式,包括内核启动参数、模块参数与 sysfs、sysctl、系统调用、netlink、procfs、seq_file、debugfs和relayfs,并给出具体的例子帮助读者掌握这些技术的使用。本文是该系列文章的第一篇,它介绍了内核启动...

实战:从深度学习探究计算机视觉_小白学视觉的博客-程序员宅基地

随着计算机视觉迅猛发展,以及大量地应用,迫使企业亟需大量AI算法的高端人才。为了培养高级CV算法工程师、AI应用人才,解决企业用人需求与求职者的就业鸿沟,开课吧教研团队倾心研发出,构建AI...

推荐文章

热门文章

相关标签