如何处理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中的导入语句会创建循环导入,一种循环依赖的形式。
示例
下面的示例解释了这个问题。假设我们创建了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()
示例3.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语法。