循环导入的最好的解决方法是从架构上优化,即调整模块和模块成员变量的设计。一个好的原则是:可导出的成员变量,都不应该依赖于导入进来的成员变量。

但是在业务开发的过程中,总会遇到通过架构层面解决不了的导入问题,这时候就只能通过语言层面来解决了。

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

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

代码执行的流程:

  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_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"循环导入异常")这一句,方案一和方案二都是通过这种方法解决的。

未经允许,请不要转载

Python的循环导入问题的更多相关文章

  1. python爬虫循环导入MySql数据库

    1.开发环境 操作系统:win10    Python 版本:Python 3.5.2   MySQL:5.5.53 2.用到的模块 没有的话使用pip进行安装:pip install xxx     ...

  2. python-模块入门二(模块循环导入,区分python文件的两种用途,模块搜索路径,软件开发的目录规范)

    一.模块的循环导入问题 run.py # import m1 # 第一次导入 m1.py # 错误示范 ''' print('正在导入m1') from m2 import y #第一次导入m2 x= ...

  3. 模块的四种形式、 import和from...import、 循环导入问题、模块的搜索路径、 python文件的两种用途

    目录 模块的四种形式 模块 模块的四种形式 import和from...import 循环导入问题 模拟问题的发生: 解决方案 模块的搜索路径 Python文件的两种用途 模块的四种形式 Nike推荐 ...

  4. python 包以及循环导入

    包的认识 包通过文件夹来管理一系列功能相近的模块 包:一系列模块的集合体重点:包中一定有一个专门用来管理包中所有模块的文件包名:存放一系列模块的文件夹名字包名(包对象)存放的是管理模块的那个文件的地址 ...

  5. python—day15 包的认识、执行顺序、执行流程、循环导入、包的导入、绝对、相对导入

    一.包的认识   包通过文件夹来管理一系列功能相近的模块 ​ 包:一系列模块的集合体 重点:包中一定有一个专门用来管理包中所有模块的文件 包名:存放一系列模块的文件夹名字 包名(包对象)存放的是管理模 ...

  6. Python 1-2模块的循环导入问题

    run.py文件: import m1 # 第一次导入 # 验证解决方案一: ''' 正在导入m1 正在导入m2 ''' # print(m1.x) # print(m1.y) # 验证解决方案二: ...

  7. python循环导入的问题

    1.问题 循环导入,代码如下: from c import c def b(): print('b') b.py from b import b def a(): # from b import b ...

  8. python的循环和选择

    一.python的选择结构: python的选择结构有两种选择结构一种是单选择(if...else)另一种则是多选择结构(if ...elif...elif) 下面用代码来实现: 1.if....el ...

  9. 20181205(模块循环导入解决方案,json&pickle模块,time,date,random介绍)

    一.补充内容 循环导入 解决方案: 1.将导入的语句挪到后面. ​ 2.将导入语句放入函数,函数在定义阶段不运行 #m1.pyprint('正在导入m1')   #②能够正常打印from m2 imp ...

随机推荐

  1. windows下安装和配置redis

    1.windows下安装和配置redis 1.1 下载: 官网(linux下载地址):https://redis.io/ Windows系统下载地址:https://github.com/MSOpen ...

  2. 删除office拥有多个都需要激活的授权信息

    首先确认office目录下存在“ospp.vbs”文件,可以搜索确认文件路径. 我的是在C:\Program Files\Microsoft Office\Office16  然后以管理员身份打开cm ...

  3. git clone 报错

    1,问题 Cloning into 'project-name'... ssh: Could not resolve hostname gerrit.firewinggames.com: nodena ...

  4. Python中将array类型不按科学计数法存在文件中的方法

    直接上代码: from numpy import *import numpy as npDrug_array = zeros((708,708),dtype = int)f = open('D:\ma ...

  5. Django深度剖析-二

    WEBserver处理过程 先写个大家熟悉的socketserver例子 #! /usr/bin/env python # encoding: utf-8 """ @Au ...

  6. js+css3+HTML5拖动滑块(type="range")改变值

    最近在做一个H5的改版项目,产品和设计给出的效果中有一个拖动滑块可以改变输入值的效果,类似如下图这样: 拿到这样的设计稿后,我有点懵了,自己写一个js?去网上找一个这样的效果?自己写一个可以,只是实现 ...

  7. [Nuget]使用Nuget管理工具包

    摘要 这里演示如何使用Nuget对类库进行打包,并将类库上传到nuget上面. 步骤 1.在nuget官网注册账号,并登陆. https://www.nuget.org 2.下载Nuget.exe,并 ...

  8. 【转】http的keep-alive和tcp的keepalive区别

    http://blog.csdn.net/oceanperfect/article/details/51064574 1.HTTP Keep-Alive在http早期,每个http请求都要求打开一个t ...

  9. golang使用chan注意事项

    背景 最近老代码中遇到的一个问题,表现为: goroutine数量在高峰期上涨,上涨后平峰期将不下来.也就是goroutine泄露 使用pprof看,进程堵塞在chan chan的使用经验 在使用ch ...

  10. Windows上的字符转换之CP_ACP和CP_OEMCP

    原文地址:http://blog.sina.com.cn/s/blog_53c1950a010158mw.html Windows API函数MultiByteToWideChar用于多字节编码字符串 ...