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


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

当两个或多个模块相互依赖时,这被称为循环依赖。这是因为每个模块都是根据其他模块定义的。

下面是一个示例,展示了循环依赖

functionE():
   functionF()

以及

functionF():
   functionE()

上面显示的代码清楚地展示了循环依赖。FunctionA()调用FunctionB(),后者依赖于前者,而FunctionB()调用FunctionA()。这种循环依赖存在一些明显的问题,我们将在下一节中更详细地讨论。


循环依赖的问题

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

循环依赖也可能是诸如内存泄漏、无限递归和级联效应等问题的根源。当你的代码包含循环依赖时,调试它产生的许多潜在问题可能非常具有挑战性,如果不仔细,这种情况就会发生。

示例

当参与循环引用的任何对象的类都有一个唯一的__del__函数时,就会出现问题。以下是一个示例,展示了循环依赖导致的问题:

class Python: def __init__(self): print("Object Python is Created") def __del__(self): print("Object Python is Destroyed") class Program: def __init__(self): print("Object Program is Created") def __del__(self): print("Object Program is Destroyed") #create the two objects Py = Python() Pr = Program() #set up the circular reference Py.Pr = Pr Pr.Py = Py #delete the objects del Py del Pr

输出

在这里,Py和Pr对象都有一个自定义的__del__函数,并且相互持有引用。最终,当我们尝试手动删除对象时,__del__方法没有被调用,这表明对象没有被销毁,而是导致了内存泄漏。

在这种情况下,Python的垃圾收集器无法收集对象以进行内存清理,因为它不确定以何种顺序调用__del__函数。

Object Python is Created
Object Program is Created
Object Python is Destroyed
Object Program is Destroyed

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

循环引用可能导致内存泄漏,可以通过两种方式避免:手动清除每个引用和使用Python中的weakref()函数。

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

在Python中,weakref函数提供一个弱引用,它不足以保持对象的存活。当对对象的唯一剩余引用是弱引用时,该对象可以被垃圾收集器自由销毁,以便其内存可以被另一个对象使用。

示例

以下示例显示了如何修复循环依赖中的内存泄漏:

import weakref class Python: def __init__(self): print("Object Python is Created") def __del__(self): print("Object Python is Destroyed") class Program: def __init__(self): print("Object Program is Created") def __del__(self): print("Object Program is Destroyed") #create the two objects Py = Python() Pr = Program() #set up the weak circular reference Py.Pr = weakref.ref(Pr) Pr.Py = weakref.ref(Py) #delete the objects del Py del Pr

输出

正如你所看到的,这次使用了两个__del__方法,证明对象已成功从内存中删除。

Object Python is Created
Object Program is Created
Object Python is Destroyed
Object Program is Destroyed

通过循环导入的循环依赖

Python中的import语句会创建循环导入,这是一种循环依赖。

示例

以下示例说明了这一点。假设我们创建了3个Python文件,如下所示:

Example1.py

# module3 import module4 def func8(): module4.func4() def func9(): print('Welcome to TutorialsPoint')

Example2.py

# module4 import module4 def func4(): print('Thank You!') module3.func9()

Example3.py

# __init__.py import module3 module3.func8()

Python在导入模块时检查模块注册表,查看它是否已被导入。如果模块已注册,Python会从缓存中使用已存在的对象。模块注册表是一个已初始化模块的表,使用模块名称作为索引。sys.modules提供了对该表的访问。

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

在上面的示例中,当Python到达import module4时,它会加载并运行。但是,module3也需要module4,因为它定义了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。

通常的做法是在模块(或脚本)的开头插入所有import语句,但这并非必需,“这种方法不会违反Python语法。”

更新于:2022年11月23日

4K+ 次浏览

启动你的职业生涯

完成课程获得认证

开始
广告
© . All rights reserved.