python解决循环依赖的问题分析

目录
  • python解决循环依赖
    • 1.概述
    • 2.循环引用介绍
      • 2.1.python引入模块原理
    • 3.解决循环引用方法
      • 3.1.重构引入关系
      • 3.2.调整import语句
      • 3.3.把模块分成引入-配置-运行三个环节
      • 3.4.动态引入

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语句毕竟是有开销的,如果它出现在需要频繁执行的循环体里面,那么这种开销会更大。另外,由于动态引入会推迟代码的执行时机,有可能你代码启动很久之后,如果因为动态引入其他模块发生异常而奔溃。

到此这篇关于python解决循环依赖的文章就介绍到这了,更多相关python循环依赖内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • python如何在循环引用中管理内存

    python中通过引用计数来回收垃圾对象,在某些环形数据结构(树,图--),存在对象间的循环引用,比如树的父节点引用子节点,子节点同时引用父节点,此时通过del掉引用父子节点,两个对象不能被立即释放 需求: 如何解决此类的内存管理问题? 如何查询一个对象的引用计数? import sys sys.getrefcount(obj) # 查询引用计数必多 1 ,因为object也引用 查询对象 如何解决内存管理问题? 通过weakref,进行弱引用,当del时候,不再引用,在引用方添加weakref

  • 使用Python项目生成所有依赖包的清单方式

    1.安装所需工具 pip install pipreqs 2.进入到python项目主目录 pipreqs ./ 3.完成上面命令会生成requirements.txt 4.sudo pip install -r requirements.txt即可 补充知识:解决Python开发过程中依赖库打包问题的方法 在Python开发的过程中,经常会遇到各种各样的小问题,比如在一台计算机上调试好的程序,迁移到另外一台机子上后往往会应为工程项目依赖库的缺失而造成错误. 除了一遍又一遍对着被抛出错误去重新i

  • Python如何导出导入所有依赖包详解

    导出所有依赖包 整个环境的依赖包导出 进入项目目录,执行以下命令: pip freeze > requirements.txt 然后在当前目录是可以看到生成 "requirements.txt" 文件,可以打开看看,会发现有很多个包信息,其实这里是把你当前 python 环境的所有包的相关信息导出来了.如果我们只需导出当前项目所需的依赖包,我可以采用另外一种方式. 只导出项目所需的依赖包 进入项目目录,执行以下命令: pipreqs ./ 默认情况下,是没有安装 "pi

  • Python中循环引用(import)失败的解决方法

    前言 最近在开发智能家居项目hestia-rpi项目中,由于代码结构层级划分不合理,导致了循环引用(import)module失败的问题,错误如下: Traceback (most recent call last):   File "./main.py", line 8, in <module>     from hestiarpi.library.server import server   File "/home/pi/server/hest

  • python如何导入依赖包

    第一步:打开pycharm:File-->Settings 第二步:Project:(你的项目名)-->Project InterPreter-->点击右边的加号 第三步:在窗口中搜索要下载的依赖-->选中并点击左下角的install package即可导入依赖包 内容扩展: python 导入导出依赖包命令 程序中必须包含一个 requirements.txt 文件,用于记录所有依赖包及其精确的版本号.如果 要在另一台电脑上重新生成虚拟环境,这个文件的重要性就体现出来了,例如部署

  • python解决循环依赖的问题分析

    目录 python解决循环依赖 1.概述 2.循环引用介绍 2.1.python引入模块原理 3.解决循环引用方法 3.1.重构引入关系 3.2.调整import语句 3.3.把模块分成引入-配置-运行三个环节 3.4.动态引入 python解决循环依赖 1.概述 在使用python开发过程中在引入其他模块时可能都经历过一个异常就是循环引用most likely due to a circular import,它的意思就是A引用了B,反过来B又引用了A,导致出现了循环引用异常.下面来介绍如何避

  • 你知道怎么用Spring的三级缓存解决循环依赖吗

    目录 1. 前言 2. Spring Bean的循环依赖 3. Spring中三大循环依赖场景演示 3.1 构造器注入循环依赖 3.2 singleton模式field属性注入循环依赖 3.3 prototype模式field属性注入循环依赖 4. Spring解决循环依赖的原理分析 4.1 Spring创建Bean的流程 4.2 Spring容器的“三级缓存” 4.3 源码解析 4.4 流程总结 5. 总结 1. 前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对

  • 浅谈Spring 解决循环依赖必须要三级缓存吗

    我们都知道 Spring 是通过三级缓存来解决循环依赖的,但是解决循环依赖真的需要使用到三级缓冲吗?只使用两级缓存是否可以呢?本篇文章就 Spring 是如何使用三级缓存解决循环依赖作为引子,验证两级缓存是否可以解决循环依赖. 循环依赖 既然要解决循环依赖,那么就要知道循环依赖是什么.如下图所示: 通过上图,我们可以看出: A 依赖于 B B 依赖于 C C 依赖于 A public class A { private B b; } public class B { private C c; }

  • spring如何解决循环依赖问题详解

    循环依赖其实就是循环引用,很多地方都说需要两个或则两个以上的bean互相持有对方最终形成闭环才是循环依赖,比如A依赖于B,B依赖于C,C又依赖于A.其实一个bean持有自己类型的属性也会产生循环依赖. setter singleton循环依赖 使用 SingleSetterBeanA依赖SingleSetterBeanB,SingleSetterBeanB依赖SingleSetterBeanA. @Data public class SingleSetterBeanA { @Autowired

  • spring如何快速稳定解决循环依赖问题

    循环依赖其实就是循环引用,很多地方都说需要两个或则两个以上的bean互相持有对方最终形成闭环才是循环依赖,比如A依赖于B,B依赖于C,C又依赖于A.其实一个bean持有自己类型的属性也会产生循环依赖. setter singleton循环依赖 使用 SingleSetterBeanA依赖SingleSetterBeanB,SingleSetterBeanB依赖SingleSetterBeanA. @Data public class SingleSetterBeanA { @Autowired

  • Spring为何需要三级缓存解决循环依赖详解

    目录 前言 bean生命周期 三级缓存解决循环依赖 总结 前言 在使用spring框架的日常开发中,bean之间的循环依赖太频繁了,spring已经帮我们去解决循环依赖问题,对我们开发者来说是无感知的,下面具体分析一下spring是如何解决bean之间循环依赖,为什么要使用到三级缓存,而不是二级缓存 bean生命周期 首先大家需要了解一下bean在spring中的生命周期,bean在spring的加载流程,才能够更加清晰知道spring是如何解决循环依赖的 我们在spring的BeanFacto

  • 浅谈Spring解决循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种:构造器参数循环依赖 表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyIn CreationException异常表示循环依赖. 如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建Test

  • 浅谈Spring如何解决循环依赖的问题

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的.这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够一下子思考出个中奥秘.本文主要针对这个问题,从源码的角度对其实现原理进行讲解. 1. 过程演示 关于Spring bean的创建,其本质上还是一个对象的创建,既然是对象,读者朋友一定要明白一点就是,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化.在Spring中,对象的实例化是通过反射实

  • Spring如何解决循环依赖的问题

    前言 在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的.这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,主要还是考察的应聘者有没有研究过spring的源码.但是说实话,spring的源码其实非常复杂的,研究起来并不是个简单的事情,所以我们此篇文章只是为了解释清楚Spring是如何解决循环依赖的这个问题. 什么样的依赖算是循环依赖? 用过Spring框架的人都对依赖注入这个词不陌生,一个Java类A中存在一个属性是类B的一个对象,那么我

  • spring解决循环依赖的简单方法

    Spring内部如何解决循环依赖,一定是单默认的单例Bean中,属性互相引用的场景.比如几个Bean之间的互相引用: 或者 setter方式原型,prototype 原型(Prototype)的场景是不支持循环依赖的,因为"prototype"作用域的Bean,为每一个bean请求提供一个实例,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean,会抛出异常. 构造器参数循环依赖 Spring容器会将每一个正在创建的Bean 标识符放在一个"当前创建Bean池&q

随机推荐