How can I avoid circular imports in Python?
I'm having a problem with circular imports. I have three Python test modules: robot_test.py which is my main script, then two auxiliary modules, controller_test.py and servo_test.py. The idea is that I want controller_test.py to define a class for my microcontroller and servo_test.py to define a class for my servos. I then want to instantiate these classes in robot_test.py. Here are my three test modules:
""" 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
If I run robot_test.py, I get the expected printout:
controller servo
However, now comes the twist. In reality, the servo_test.py module depends on controller_test.py by way of robot_test.py. This is because my servo definitions require an already-instantiated controller object before they themselves can be instantiated. But I'd like to keep all the initial instantiations in robot_test.py. So I tried modifying my servo_test.py script as follows:
""" servo_test.py """
from pi.nodes.robots.robot_test import myController
class Servo():
def __init__(self, id="servo"):
self.ID = id
print myController.ID
Of course, I could sense that the circularity was going to cause problems and sure enough, when I now try to run robot_test.py I get the error:
ImportError: Cannot import name Servo
which in turn is caused by servo_test.py returning the error:
ImportError: Cannot import name myController
In C# I would define myController and myServo as static objects in robot_test.py and then I could use them in other classes. Is there anyway to do the same in Python? One workaround I have found is to pass the myController object to the Servo class a开发者_如何学运维s an argument, but I was hoping to avoid having to do this.
Thanks!
One workaround I have found is to pass the myController object to the Servo class as an argument, but I was hoping to avoid having to do this.
Why ever would you want to avoid it? It's a classic case of a crucial Design Pattern (maybe the most important one that wasn't in the original Gang of Four masterpiece), Dependency Injection.
DI implementation alternatives to having the dependency in the initializer include using a setter method (which completes another crucial non-Gof4 DP, two-phase construction, started in the __init__
) -- that avoids another circularity problem not related to imports, when A's instances need a B and B's instances need an A, in each case to complete what's logically the instances "initialization". But when you don't need two-phase construction, initializers are the natural place to inject dependencies.
Beyond the advantages related to breaking circularity, DI facilitates reuse (by generalization: e.g, if and when you need to have multiple controllers and servos rather than just one of each, it allows you to easily control the "pairing up" among them) and testing (it becomes easy to isolate each class for testing purposes by injecting in it a mock of the other, for example).
What's not to like?
servo_test.py
doesn't actually need myController
, since the access is within a function. Import the module instead, and access myController
via that module.
import pi.nodes.robots.robot_test as robot_test
class Servo():
def __init__(self, id="servo"):
self.ID = id
print robot_test.myController.ID
This will work as long as myController
exists before Servo
is instantiated.
Pass the instantiated controller object as an init argument to the instantiation of the 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)
精彩评论