如何处理Python类之间的循环依赖?

如何处理Python类之间的循环依赖?

在本文中,我们将讨论如何处理Python类之间的循环依赖。首先,让我们了解什么是循环依赖。

当两个或更多模块相互依赖时,就会出现循环依赖关系。这是因为每个模块都是基于其他模块定义的。

以下是循环依赖的一个示例。

functionE():
   functionF()

并且

functionF():
   functionE()

上面的代码清楚地显示了循环依赖。FunctionA()调用FunctionB(),FunctionB()依赖于它,FunctionB()调用FunctionA()。这种类型的循环依赖存在一些明显的问题,我们将在以下部分详细介绍。

如何处理Python类之间的循环依赖?

更多Python相关文章,请阅读:Python 教程

循环依赖的问题

循环依赖可能会导致代码出现各种问题。例如,它可以导致模块之间紧密耦合,限制代码重复使用。这方面也使长期代码维护更具挑战性。

循环依赖还可能导致可能的问题,如内存泄漏,无限递归和级联效应。如果不小心,您的代码包含循环依赖,则可能会产生许多可能的问题,这可能非常具有挑战性。

示例

当循环引用中涉及的任何对象的类具有唯一的del函数时,问题就会出现。以下是一个示例,显示循环依赖导致的问题:

class Python:
   def __init__(self):
      print("对象Python已创建")
   def __del__(self):
      print("对象Python已销毁")
class Program: 
   def __init__(self): 
      print("对象Program已创建") 
   def __del__(self): 
      print("对象Program已销毁") 

#创建两个对象
Py = Python() 
Pr = Program() 
#建立循环引用
Py.Pr = Pr 
Pr.Py = Py 
#删除对象
del Py 
del Pr

输出

在这里,Py和Pr两个对象都有一个自定义的del函数,并且彼此持有引用。最后,在我们尝试手动删除对象时,del方法没有被调用,这表明对象没有被销毁,而是导致了内存泄漏。 在这种情况下,Python的垃圾回收器无法收集对象进行内存清理,因为它不确定何种顺序调用del函数。

对象Python已创建
对象Program已创建

修复Python中循环依赖的内存泄漏问题

循环引用可能会导致内存泄漏,可以通过两种方法避免,即手动擦除每个引用并在Python中利用weakref()函数。

手动删除每个引用并不是一个理想的选择,因为 weakref() 排除了程序员考虑删除引用的点的需要。

在 Python 中, weakref 函数提供的是无法维护对象生命周期的弱引用。当对一个对象的引用只剩下弱引用时,这个项目可以被垃圾回收器自由销毁,以便其内存可以被另一个对象使用。

示例

下面的示例显示了如何解决循环依赖中的内存泄漏问题。

import weakref

class Python:
   def __init__(self):
      print("创建 Python 对象")

   def __del__(self):
      print("销毁 Python 对象")

class Program:
   def __init__(self):
      print("创建 Program 对象")

   def __del__(self):
      print("销毁 Program 对象")


# 创建两个对象
Py = Python()
Pr = Program()

# 建立弱循环引用
Py.Pr = weakref.ref(Pr)
Pr.Py = weakref.ref(Py)

# 删除对象
del Py
del Pr

输出

正如您所看到的,这一次两个 del 方法都被用到了,证明对象已经成功地从内存中删除。

创建 Python 对象
创建 Program 对象
销毁 Python 对象
销毁 Program 对象

循环依赖通过循环导入

在 Python 中,import 语句会产生循环导入,一种循环依赖。

示例

下面的示例说明了这一点。假设我们已经创建了三个 Python 文件,如下所示−

Example1.py

# module3
import module4

def func8():
   module4.func4()

def func9():
   print('欢迎来到 TutorialsPoint')

Example2.py

# module4
import module4

def func4():
   print('谢谢!')
   module3.func9()

Example3.py

# __init__.py
import module3

module3.func8()

在导入模块时,Python 会检查模块注册表,以查看它是否已经被导入。如果模块已经注册,Python 将从缓存中使用之前存在的对象。模块注册表是一个已初始化模块的表格,可以使用模块名称对其进行索引。 sys.modules 提供了访问这个表的途径。

如果未注册该模块,Python会找到它,必要时初始化它,然后在新模块的命名空间中执行它。

在上面的示例中,当Python到达import module4时,它会加载并运行。但是,module4也被module3所需,因为它定义了func8()。

输出

当func4()尝试调用module3()的func9时,就会出现问题。由于func9()尚未定义并且module3在module4之前加载,因此会返回错误 −

$ python __init__.py
Thank You!
Traceback (most recent call last):
   File "__init__.py", line 3, in 
   Module3.func8()
File "C:\Users\Lenovo\Desktop\module3\__init__.py", line 5, in func8
   Module4.func4()
File "C:\Users\Lenovo\Desktop\module4\__init__.py", line 6, in func4
   module4.func9()
AttributeError: 'module' object has no attribute 'func9'

修复上述循环依赖

循环导入通常是设计不良的结果。 对程序的更详细分析可能显示出,该依赖性实际上并不是必需的,或者可以将依赖功能转移到其他模块而不含有循环引用。

有时将两个模块组合成单个更大的模块是一个简单的选项。

示例

上述示例的最终代码将类似于上面给出的解释−

# module 3 & 4
def func8():
   func4()
def func4():
   print('Welcome to TutorialsPoint')
   func9()
def func9():
   print('Thank You!')
func8()

输出

以下是上述代码的输出 −

Welcome to TutorialsPoint
Thank You!

注意 − 但是,如果这两个模块已经包含了很多代码,则合并的模块可能具有一些不相关的函数(紧密耦合),并且可能会显著增长。

如果不起作用,另一个选项是延迟导入module4,并在必要时才导入它。为此,将module4的导入包含在func8()定义中,如下所示−

# module 3
def func8():
   import module4
   module4.func4()
def func9():
   print('Thank You!')

在上面的情况下,Python将能够加载module3中的所有函数,并仅在必要时加载module4。

将所有导入语句插入到模块(或脚本)的开头是惯例但不是必要的,“这种方法不违反Python语法。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程