如何避免Python中的循环导入?

0 投票
3 回答
2133 浏览
提问于 2025-04-16 00:37

我遇到了一个关于循环导入的问题。我有三个Python测试模块:robot_test.py是我的主脚本,还有两个辅助模块,controller_test.py和servo_test.py。我的想法是让controller_test.py定义一个微控制器的类,而servo_test.py定义一个伺服电机的类。然后我想在robot_test.py中实例化这些类。以下是我的三个测试模块:

""" robot_test.py """    
from pi.nodes.actuators.servo_test import Servo
from pi.nodes.controllers.controller_test import Controller

myController = Controller()
myServo = Servo()

print myController.ID, myServo.ID


""" controller_test.py """
class Controller():
    def __init__(self, id="controller"):
        self.ID = id


""" servo_test.py """
class Servo():
    def __init__(self, id="servo"):
        self.ID = id

当我运行robot_test.py时,得到了预期的输出:

controller servo

但是,事情来了个反转。实际上,servo_test.py模块依赖于controller_test.py,而这个依赖是通过robot_test.py实现的。这是因为我的伺服电机定义需要一个已经实例化的控制器对象,才能自己被实例化。但我希望把所有的实例化都放在robot_test.py中。所以我尝试修改我的servo_test.py脚本,如下所示:

""" servo_test.py """
from pi.nodes.robots.robot_test import myController
class Servo():
    def __init__(self, id="servo"):
        self.ID = id        
        print myController.ID

当然,我感觉到这种循环关系会引发问题,果然,当我现在尝试运行robot_test.py时,出现了错误:

ImportError: Cannot import name Servo

这个错误是因为servo_test.py返回了另一个错误:

ImportError: Cannot import name myController

在C#中,我会在robot_test.py中将myController和myServo定义为静态对象,然后可以在其他类中使用它们。在Python中有没有办法做到这一点?我找到的一个解决方法是将myController对象作为参数传递给Servo类,但我希望能避免这样做。

谢谢!

3 个回答

0

servo_test.py 其实并不 需要 myController,因为它是在一个函数内部访问的。你可以直接导入这个模块,然后通过这个模块来访问 myController

import pi.nodes.robots.robot_test as robot_test
class Servo():
    def __init__(self, id="servo"):
        self.ID = id        
        print robot_test.myController.ID

只要 myController 在创建 Servo 之前存在,这样做就没问题。

0

把已经创建好的控制器对象当作初始化参数,传递给伺服器的实例化过程。

""" servo_test.py """
class Servo():
    def __init__(self,controller,id="servo"):
        self.ID = id
        self.ctrl = controller
        print self.ctrl.ID

""" robot_test.py """    
from pi.nodes.actuators.servo_test import Servo
from pi.nodes.controllers.controller_test import Controller

myController = Controller()
myServo = Servo(myController)
4

我找到的一个解决办法是把 myController 对象作为参数传给 Servo 类,但我希望能避免这样做。

你为什么想要避免这样做呢?这其实是一个经典的设计模式案例(可能是最重要的一个,但在最初的《四人帮》经典书籍中没有提到),叫做 依赖注入

依赖注入的实现方式有很多,除了在初始化时直接传入依赖对象,还可以使用一个设置方法(这也是另一个重要的非《四人帮》设计模式,叫做 两阶段构造,从 __init__ 开始)——这样可以避免一个与导入无关的循环问题,即当 A 的实例需要 B,而 B 的实例又需要 A 时,这样的情况会影响到实例的“初始化”。不过,当你不需要两阶段构造时,初始化器是注入依赖的自然选择。

除了打破循环依赖带来的好处,依赖注入还方便重用(通过泛化:例如,如果你需要多个控制器和伺服器,而不仅仅是各一个,它可以让你轻松控制它们之间的“配对”)和测试(通过注入一个模拟对象,你可以轻松地隔离每个类进行测试)。

这有什么不好呢?

撰写回答