網(wǎng)站首頁 編程語言 正文
python解決循環(huán)依賴
1.概述
在使用python開發(fā)過程中在引入其他模塊時可能都經(jīng)歷過一個異常就是循環(huán)引用most likely due to a circular import,它的意思就是A引用了B,反過來B又引用了A,導致出現(xiàn)了循環(huán)引用異常。下面來介紹如何避免循環(huán)引用異常。
2.循環(huán)引用介紹
2.1.python引入模塊原理
下面通過一個循環(huán)引用示例,來介紹python引入模塊的原理。示例中創(chuàng)建了三個模塊,它的引用關系如下
- dialog.py模塊引入了app模塊的prefs類的get方法
- app模塊引入了dialog模塊的show方法
創(chuàng)建一個python文件,命名為dialog.py
import app class Dialog: def __init__(self, save_dir): self.save_dir = save_dir save_dialog = Dialog(app.prefs.get('save_dir')) def show(): print('Showing the dialog!')
創(chuàng)建一個python文件,命名為app.py
import dialog class Prefs: def get(self, name): pass prefs = Prefs() dialog.show()
創(chuàng)建一個python文件,命名為main.py
import app
運行上面循環(huán)引用代碼,拋出了異常
AttributeError: partially initialized module 'app' has no attribute 'prefs' (most likely due to a circular import)
要明白上面為什么會拋出循環(huán)引用異常,首先要明白python是如何引入模塊的。在引入模塊的時候,python系統(tǒng)會按照深度優(yōu)先的順序,對模塊執(zhí)行以下五步:
- 1.在sys.path里尋找模塊的位置
- 2.把模塊的代碼加載進來,并確認這些代碼能編譯
- 3.創(chuàng)建響應的空白模塊對象表示該模塊
- 4.把這個模塊插入sys.modules字典
- 5.運行模塊對象之中的代碼定義該模塊的內容
循環(huán)依賴之所以會出錯,原因在于,執(zhí)行完第4步驟之后,這個模塊已經(jīng)位于sys.modules之中了,然而它的內容還沒有得到定義,要等到執(zhí)行完第5步驟,才能齊備。
可是python在執(zhí)行import語句的時候,如果發(fā)現(xiàn)要引用的模塊已經(jīng)出現(xiàn)在了sys.modules之中,(也就是執(zhí)行完第4個步驟),那么就會繼續(xù)執(zhí)行importd 下一條語句,而不會顧及模塊之中的內容是否的得到了定義。
例如上面的例子,app模塊在執(zhí)行自己第5步驟時,首先遇到的就是引入dialog模塊的這條語句,而此刻他還沒有把自己的內容定義出來,他只不過執(zhí)行完了前4步驟,讓自己出現(xiàn)在了sys.dodules字典里面而已。
等到dialog模塊反過來要引入app的時候,由于app模塊已經(jīng)出現(xiàn)在了sys.modules字典中,python就會認為這個模塊已近引入,于是繼續(xù)執(zhí)行dialog模塊其他代碼,而不會考慮app里面的內容到底有沒有定義。
這樣的話,執(zhí)行到save_dialog = Dialog(app.prefs.get('save_dir'))
這一句的時候,就會因為app里面找不到prefs屬性而出錯。(這個屬性必須等app執(zhí)行完第5步驟才能夠得到定義)
3.解決循環(huán)引用方法
如果要解決上面的循環(huán)引用異常,有四種解決辦法。
3.1.重構引入關系
例如把prefs內容提取到一個單獨的工具模塊中,把它放在依賴體系最底層,這樣app與dialog分別引入這個模塊。他們的關系如下
- app 引入 prefs
- dialog 引入 prefs
有時候這種重構引入關系需要拆分代碼,對于大型的項目可能不太好拆分,還可以通過其他的方式解決
3.2.調整import語句
調整import位置,例如我們可以讓app模塊不要那么早就引入dialog模塊,而是等到prefs等其他內容都創(chuàng)建出來之后,在引入dailog,這樣的話,等待dialog返回來使用app中的屬性時,就不會因為該屬性還沒有定義出來而發(fā)生AttributeError
class Prefs: def get(self, name): pass prefs = Prefs() import dialog # Moved dialog.show()
這種寫法雖然可行,但是它違背了PEP8規(guī)范,依照建議,所有的import語句都應該出現(xiàn)在文件開頭。這種方式有個弊端,在執(zhí)行了一半,才發(fā)現(xiàn)自己要使用的那個模塊還沒有加載進來,因此不建議使用這種方法。
3.3.把模塊分成引入-配置-運行三個環(huán)節(jié)
循環(huán)引入可以通過勁量縮減引用時所要執(zhí)行的操作。我們可以讓模塊只把函數(shù)、類、與常量定義出來,而不真正去執(zhí)行,這樣python在引入本模塊的時候,就不會由于操作其他模塊而出錯了。
我們可以把本模塊里,需要用到其他模塊的那種操作放在configure函數(shù)中,等到模塊徹底引入完畢后,再去調用。
dialog.py模塊把調用的操作放在configure函數(shù)中
import app class Dialog: def __init__(self): pass save_dialog = Dialog() def show(): print('Showing the dialog!') def configure(): save_dialog.save_dir = app.prefs.get('save_dir')
app.py模塊把調用的操作放在configure函數(shù)中
import dialog class Prefs: def get(self, name): pass prefs = Prefs() def configure(): pass
main.py模塊按照引入-配置-運行的順序先把那兩個模塊引入進來,然后調用各自的configure函數(shù),最后運行dialog模塊的show函數(shù)
import app import dialog app.configure() dialog.configure() dialog.show()
這種寫法能適應許多種情況,而且便于我們運用依賴注入模式來替換受依賴模塊之中的內容。
但是有時候不太容易從代碼中抽離出這樣一個configure配置環(huán)節(jié),因為他把該模塊定義的對象與這些對象的配置邏輯分別寫到了兩個環(huán)節(jié)里面。
3.4.動態(tài)引入
動態(tài)引入比前幾個方法要簡單,也就是把import語句從模塊級別下移到函數(shù)或方法里面,這樣就解決了循環(huán)依賴關系了。
這種import并不會在程序啟動并初始化本模塊時執(zhí)行,而是等到相關函數(shù)真正運行的時候才得以觸發(fā),因此又叫做動態(tài)引入
下面我們用動態(tài)引入辦法修改dialog模塊,他只會在dialog.show函數(shù)真正運行的時候去引入import模塊,而不像原來那樣,模塊剛初始化,就要引入app
class Dialog: def __init__(self): pass # Using this instead will break things # save_dialog = Dialog(app.prefs.get('save_dir')) save_dialog = Dialog() def show(): import app # Dynamic import save_dialog.save_dir = app.prefs.get('save_dir') print('Showing the dialog!')
app模塊修改
import dialog class Prefs: def get(self, name): pass prefs = Prefs() dialog.show()
main模塊
import
這樣寫,實際上與剛才那種先引入,再配置,然后運行的辦法是類似的。區(qū)別僅僅在于,這次不調整代碼的結構,也不修改模塊的定義與引入方式,只是把形成循環(huán)依賴的那條import語句推遲到真正需要使用另外一個模塊的那一刻。
一般來說還是勁量避免動態(tài)引入,因為import語句畢竟是有開銷的,如果它出現(xiàn)在需要頻繁執(zhí)行的循環(huán)體里面,那么這種開銷會更大。另外,由于動態(tài)引入會推遲代碼的執(zhí)行時機,有可能你代碼啟動很久之后,如果因為動態(tài)引入其他模塊發(fā)生異常而奔潰。
原文鏈接:https://blog.csdn.net/m0_38039437/article/details/128138739
相關推薦
- 2022-01-31 element-ui upload組件 上傳文件類型限制
- 2022-07-07 C語言用Easyx繪制圍棋和象棋的棋盤_C 語言
- 2022-03-28 .Net?Core依賴注入IOC和DI介紹_實用技巧
- 2022-10-11 Xshell連接centOS7并與CentOS7聯(lián)網(wǎng)_Linux
- 2022-09-17 python生成requirements.txt文件的推薦方法_python
- 2022-03-29 基于Python+Tkinter實現(xiàn)一個簡易計算器_python
- 2022-09-27 使用Python?matplotlib繪制簡單的柱形圖、折線圖和直線圖_python
- 2022-07-21 SQL查詢出的兩列合并成一列顯示
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支