如何处理 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, inModule3.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 语法。