python解决循环依赖的问题分析
作者:Bruce小鬼 发布时间:2023-03-19 12:22:14
python解决循环依赖
1.概述
在使用python开发过程中在引入其他模块时可能都经历过一个异常就是循环引用most likely due to a circular import,它的意思就是A引用了B,反过来B又引用了A,导致出现了循环引用异常。下面来介绍如何避免循环引用异常。
2.循环引用介绍
2.1.python引入模块原理
下面通过一个循环引用示例,来介绍python引入模块的原理。示例中创建了三个模块,它的引用关系如下
dialog.py模块引入了app模块的prefs类的get方法
app模块引入了dialog模块的show方法
创建一个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!')
创建一个python文件,命名为app.py
import dialog
class Prefs:
def get(self, name):
pass
prefs = Prefs()
dialog.show()
创建一个python文件,命名为main.py
import app
运行上面循环引用代码,抛出了异常
AttributeError: partially initialized module 'app' has no attribute 'prefs' (most likely due to a circular import)
要明白上面为什么会抛出循环引用异常,首先要明白python是如何引入模块的。在引入模块的时候,python系统会按照深度优先的顺序,对模块执行以下五步:
1.在sys.path里寻找模块的位置
2.把模块的代码加载进来,并确认这些代码能编译
3.创建响应的空白模块对象表示该模块
4.把这个模块插入sys.modules字典
5.运行模块对象之中的代码定义该模块的内容
循环依赖之所以会出错,原因在于,执行完第4步骤之后,这个模块已经位于sys.modules之中了,然而它的内容还没有得到定义,要等到执行完第5步骤,才能齐备。
可是python在执行import语句的时候,如果发现要引用的模块已经出现在了sys.modules之中,(也就是执行完第4个步骤),那么就会继续执行importd 下一条语句,而不会顾及模块之中的内容是否的得到了定义。
例如上面的例子,app模块在执行自己第5步骤时,首先遇到的就是引入dialog模块的这条语句,而此刻他还没有把自己的内容定义出来,他只不过执行完了前4步骤,让自己出现在了sys.dodules字典里面而已。
等到dialog模块反过来要引入app的时候,由于app模块已经出现在了sys.modules字典中,python就会认为这个模块已近引入,于是继续执行dialog模块其他代码,而不会考虑app里面的内容到底有没有定义。
这样的话,执行到save_dialog = Dialog(app.prefs.get('save_dir'))
这一句的时候,就会因为app里面找不到prefs属性而出错。(这个属性必须等app执行完第5步骤才能够得到定义)
3.解决循环引用方法
如果要解决上面的循环引用异常,有四种解决办法。
3.1.重构引入关系
例如把prefs内容提取到一个单独的工具模块中,把它放在依赖体系最底层,这样app与dialog分别引入这个模块。他们的关系如下
app 引入 prefs
dialog 引入 prefs
有时候这种重构引入关系需要拆分代码,对于大型的项目可能不太好拆分,还可以通过其他的方式解决
3.2.调整import语句
调整import位置,例如我们可以让app模块不要那么早就引入dialog模块,而是等到prefs等其他内容都创建出来之后,在引入dailog,这样的话,等待dialog返回来使用app中的属性时,就不会因为该属性还没有定义出来而发生AttributeError
class Prefs:
def get(self, name):
pass
prefs = Prefs()
import dialog # Moved
dialog.show()
这种写法虽然可行,但是它违背了PEP8规范,依照建议,所有的import语句都应该出现在文件开头。这种方式有个弊端,在执行了一半,才发现自己要使用的那个模块还没有加载进来,因此不建议使用这种方法。
3.3.把模块分成引入-配置-运行三个环节
循环引入可以通过劲量缩减引用时所要执行的操作。我们可以让模块只把函数、类、与常量定义出来,而不真正去执行,这样python在引入本模块的时候,就不会由于操作其他模块而出错了。
我们可以把本模块里,需要用到其他模块的那种操作放在configure函数中,等到模块彻底引入完毕后,再去调用。
dialog.py模块把调用的操作放在configure函数中
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函数中
import dialog
class Prefs:
def get(self, name):
pass
prefs = Prefs()
def configure():
pass
main.py模块按照引入-配置-运行的顺序先把那两个模块引入进来,然后调用各自的configure函数,最后运行dialog模块的show函数
import app
import dialog
app.configure()
dialog.configure()
dialog.show()
这种写法能适应许多种情况,而且便于我们运用依赖注入模式来替换受依赖模块之中的内容。
但是有时候不太容易从代码中抽离出这样一个configure配置环节,因为他把该模块定义的对象与这些对象的配置逻辑分别写到了两个环节里面。
3.4.动态引入
动态引入比前几个方法要简单,也就是把import语句从模块级别下移到函数或方法里面,这样就解决了循环依赖关系了。
这种import并不会在程序启动并初始化本模块时执行,而是等到相关函数真正运行的时候才得以触发,因此又叫做动态引入
下面我们用动态引入办法修改dialog模块,他只会在dialog.show函数真正运行的时候去引入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
这样写,实际上与刚才那种先引入,再配置,然后运行的办法是类似的。区别仅仅在于,这次不调整代码的结构,也不修改模块的定义与引入方式,只是把形成循环依赖的那条import语句推迟到真正需要使用另外一个模块的那一刻。
一般来说还是劲量避免动态引入,因为import语句毕竟是有开销的,如果它出现在需要频繁执行的循环体里面,那么这种开销会更大。另外,由于动态引入会推迟代码的执行时机,有可能你代码启动很久之后,如果因为动态引入其他模块发生异常而奔溃。
来源:https://blog.csdn.net/m0_38039437/article/details/128138739
猜你喜欢
- Python是一门非常酷的语言,因为很少的Python代码可以在短时间内做很多事情,并且,Python很容易就能支持多任务和多重处理。py&
- 说实话,对于移除这个旧有功能对于我来说,我是欢心鼓舞的。因为我在开发和应用当中一向不用expression来处理,虽然它确实是非常方便,比如
- 今天我和中国著名画家"渔人"谈了一个关于"怎样才能设计好"的问题,他给我说了一句话,得益不浅,那句话
- 在实现TextStraem的时候,找到判断文件编码的代码是VBS的,但是在JScript中是没有ASC等函数的,也不能对二进制数据进行处理,
- 有时候要通过asp代码在数据库中创建表和列,下面的就是这些操作的函数。1.检测表是否存在tbName 检测的表的名称dbTp 数据库的类型1
- 看着这个题目可能大家会有点陌生,不知道是什么,说的直白一点,RDFa就是让你的页面更好的被机器所理解,而不简简单单是展示你的页面,下面就关于
- 1. DID(Differences-in-Differences)定义双重差分法,其主要被用于社会学中的政策效果评估。这种方法需要两个「差
- 图片外框特征参数:①dashed:虚线 ②dotted:点虚线 ③solid:实线 ④double:双线 ⑤groove:沟槽状 ⑥ridg
- 一、定位 oracle分两大块,一块是开发,一块是管理。开发主要是写写存储过程、触发器什么的,还有就是用Oracle的Develop工具做f
- 下表列出 SQL Server 查询分析器提供的所有键盘快捷方式。活动 快捷方式 书签:清除所有书签。 CTRL-SHIFT-F2
- django的url采用正则表达式进行配置,虽然强大却也广为诟病。反对者们认为django的url配置过于繁琐,且不支持默认的路由功能。我倒
- strip_tags定义和用法strip_tags() 函数剥去字符串中的 HTML、XML 以及 PHP 的标签。注释:该函数始终会剥离
- 本文实例为大家分享了python实现五子棋游戏的具体代码,供大家参考,具体内容如下# -*- coding:utf-8 -*-# @Time
- QQ影音至9月发布之后一直口碑很好,视觉界面和交互设计也颇受好评,这是设计师们努力的结果,也是众多用户反馈的功劳。我们希望可以提供更多价值回
- 产品通常分两种,一种是遵循现有用户习惯,一种是颠覆用户习惯。至于什么是用户习惯,你现在用右手还是左手操作鼠标,这就是你的习惯。很多公司团队专
- 人们对于那些抄袭模仿的网站有诸多抱怨,但在这篇文章中,却没有冷嘲热讽的意思。但正如他们所说,“模仿是最为忠诚的奉承形式”。“如果你确实需要借
- 内容摘要:这篇文章的主旨是弄清楚如何根据实际需求实现一个联动菜单以及联动菜单的原理,实例是实现一个日期选择下拉菜单。首先来分析一下日期下拉菜
- 本文实例为大家分享了python实现自动打卡小程序的具体代码,供大家参考,具体内容如下"""湖南大学疫情防控每
- 本文实例讲述了php基于websocket搭建简易聊天室实践。分享给大家供大家参考。具体如下:1、前言公司游戏里面有个简单的聊天室,了解了之
- 图片的宽度和高度是未知的,没有一个固定的尺寸,在这个前提下要使图片在一个固定了宽度和高度的容器中垂直居中,想想感觉还是挺麻烦的,由于最近的项