如何避免Python中的循环导入?
我遇到了一个关于循环导入的问题。我有三个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 个回答
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
之前存在,这样做就没问题。
把已经创建好的控制器对象当作初始化参数,传递给伺服器的实例化过程。
""" 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)
我找到的一个解决办法是把 myController 对象作为参数传给 Servo 类,但我希望能避免这样做。
你为什么想要避免这样做呢?这其实是一个经典的设计模式案例(可能是最重要的一个,但在最初的《四人帮》经典书籍中没有提到),叫做 依赖注入。
依赖注入的实现方式有很多,除了在初始化时直接传入依赖对象,还可以使用一个设置方法(这也是另一个重要的非《四人帮》设计模式,叫做 两阶段构造,从 __init__
开始)——这样可以避免一个与导入无关的循环问题,即当 A 的实例需要 B,而 B 的实例又需要 A 时,这样的情况会影响到实例的“初始化”。不过,当你不需要两阶段构造时,初始化器是注入依赖的自然选择。
除了打破循环依赖带来的好处,依赖注入还方便重用(通过泛化:例如,如果你需要多个控制器和伺服器,而不仅仅是各一个,它可以让你轻松控制它们之间的“配对”)和测试(通过注入一个模拟对象,你可以轻松地隔离每个类进行测试)。
这有什么不好呢?