Python Circular Imports
什么是循环依赖?
当两个或更多模块相互依赖时,就会发生循环依赖。 这是因为每个模块都是根据另一个模块定义的(见图1)。
例如:
1 2 | functionA(): functionB() |
和
1 2 | functionB(): functionA() |
上面的代码描述了一个相当明显的循环依赖。
图1
循环依赖问题
循环依赖关系会在代码中引起很多问题。 例如,它可能会在模块之间产生紧密的耦合,从而降低代码的可重用性。 从长远来看,这一事实也使代码难以维护。
此外,循环依赖关系可能是潜在故障的根源,例如无限递归,内存泄漏和级联效应。 如果您不小心,并且代码中有循环依赖关系,则调试它可能导致的许多潜在问题可能非常困难。
什么是循环进口?
循环导入是循环依赖的一种形式,它是使用Python中的import语句创建的。
例如,让我们分析以下代码:
1 2 3 4 5 6 7 8 | # module1 import module2 def function1(): module2.function2() def function3(): print('Goodbye, World!') |
1 2 3 4 5 6 | # module2 import module1 def function2(): print('Hello, World!') module1.function3() |
1 2 3 4 5 | # __init__.py import module1 module1.function1() |
当Python导入模块时,它将检查模块注册表以查看模块是否已导入。 如果模块已经注册,Python将使用缓存中的现有对象。 模块注册表是已按模块名称初始化和索引的模块表。 可以通过
如果尚未注册,Python会找到该模块,并在必要时对其进行初始化,然后在新模块的名称空间中执行该模块。
在我们的示例中,当Python到达
当
1 2 3 4 5 6 7 8 9 10 | $ python __init__.py Hello, World! Traceback (most recent call last): File"__init__.py", line 3, in <module> module1.function1() File"/Users/scott/projects/sandbox/python/circular-dep-test/module1/__init__.py", line 5, in function1 module2.function2() File"/Users/scott/projects/sandbox/python/circular-dep-test/module2/__init__.py", line 6, in function2 module1.function3() AttributeError: 'module' object has no attribute 'function3' |
如何解决循环依赖
通常,圆形进口是不良设计的结果。 对程序进行更深入的分析可能会得出结论,实际上并不需要依赖项,或者可以将依赖的功能移到不包含循环引用的其他模块中。
一个简单的解决方案是有时两个模块都可以合并为一个更大的模块。 上面示例中的结果代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | # module 1 & 2 def function1(): function2() def function2(): print('Hello, World!') function3() def function3(): print('Goodbye, World!') function1() |
但是,合并的模块可能具有一些不相关的功能(紧密耦合),如果两个模块中已经有很多代码,合并后的模块可能会变得非常大。
因此,如果这不起作用,则另一种解决方案可能是推迟导入module2以便仅在需要时才导入它。 这可以通过将模块2的导入放在
1 2 3 4 5 6 7 8 | # module 1 def function1(): import module2 module2.function2() def function3(): print('Goodbye, World!') |
在这种情况下,Python将能够在module1中加载所有功能,然后仅在需要时才加载module2。
这种方法与Python语法并不矛盾,正如Python文档所述:"这是惯例,但并非必须将所有import语句放在模块(或脚本)的开头"。
Python文档还指出,建议使用
即使没有循环依赖关系,您也可能会看到许多使用延迟导入的代码库,这会加快启动时间,因此,这根本不被认为是错误的做法(尽管根据您的项目,这可能是错误的设计) 。
包起来
循环导入是循环引用的一种特殊情况。 通常,可以通过更好的代码设计来解决它们。 但是,有时,最终的设计可能包含大量代码,或者混合了不相关的功能(紧密耦合)。
您是否以自己的代码运行过循环导入? 如果是这样,您如何解决? 让我们在评论中知道!