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_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
程式碼執行的流程:
from pack1.module_a import class_a from pack2.module_b import class_b from pack1.module_a import 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"迴圈匯入異常")
這一句,方案一和方案二都是通過這種方法解決的。
未經允許,請不要轉載