tags:

views:

88

answers:

4

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 as an argument, but I was hoping to avoid having to do this.

Thanks!

A: 

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.

Ignacio Vazquez-Abrams
A: 

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)
MattH
"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."
Ignacio Vazquez-Abrams
Ooh my bad, I lost interest in reading the post when I saw 'C#', didn't expect there to be anything pertinent after it.
MattH
A: 

Thanks Ignacio for the suggestion. I just gave it a try and when I run robot_test.py I get the error:

ImportError: Cannot import name Servo

So then I tried modifying robot_test.py to make sure myController was instantiated before importing servo_test.py like this:

""" robot_test.py """
from pi.nodes.controllers.controller_test import Controller
myController = Controller()

from pi.nodes.actuators.servo_test import Servo
myServo = Servo()

print myController.ID, myServo.ID

but I got the same error...

RPG
@RPG: If you have updates to your question, you should edit your question rather than post feedback in an "answer".
MattH
Thanks @MattH--I was afraid no one would see the update at the end of such a long question...I initially posted it in a comment but apparently one cannot post formatted code in a comment.As for C#, you'll be happy to know that I am converting from C# to Python which is why I am asking these kinds of silly questions.
RPG
@RPG: Traditionally a horizontal rule and bold "UPDATED", with a quick summary of what you've tried since and what happened will maintain interest. I was being lazy, but it is better to summarise all of your "wants" in one place in your question. As the esteemed Alex Martelli so eloquently said, passing dependencies as arguments to initializers is a good way to go.
MattH
+3  A: 

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?

Alex Martelli
+1: Bah... what he said. I'll go back to lurking.
MattH
Thanks Alex--that's exactly the kind of education I was looking for!
RPG