Reputation: 921
I want to create an Abstract Factory in order to abstract hardware differences between computers (say a RaspberryPi and an Arduino) in Python 2.7.
I am using the following implementation of an Abstract Factory:
'''
Provide a device-agnostic display interface
'''
from hardware import sysname
class DisplayBase(object):
def __init__(self):
pass
def show(self, message):
pass
def __str__(self):
return "DisplayBase"
def __repr__(self):
return self.__str__()
class RPIDisplay(DisplayBase):
def __new__(cls, *args, **kwargs):
from rpi_display import writeline
instance = super(RPIDisplay, cls).__new__(cls, *args, **kwargs)
return instance
def __str__(self):
return "RPIDisplay"
def show(self, message):
writeline(message)
class ArduinoDisplay(DisplayBase):
def __new__(cls, *args, **kwargs):
import arduino_display
instance = super(ArduinoDisplay, cls).__new__(cls, *args, **kwargs)
return instance
def __str__(self):
return "ArduinoDisplay"
def show(self, message):
return arduino_display.println(message)
class Display(DisplayBase): # Display Factory
def __new__(cls, *args, **kwargs):
platform = sysname()
if platform == "RaspberryPi":
return RPIDisplay()
elif platform == "Arduino":
return ArduinoDisplay()
else:
return MockDisplay()
if __name__ == "__main__":
display = Display()
print display
display.show("hello world")
The instantiation works fine, but when I try to run this, I get:
ArduinoDisplay
Traceback (most recent call last):
File "tt.py", line 56, in <module>
display.show("hello world")
File "tt.py", line 41, in show
return arduino_display.println(message)
NameError: global name 'arduino_display' is not defined
So the import of arduino_display
does sorta work, but I cannot find a way to use it within the object.
Conditional imports are needed since different platforms will have different modules installed.
Any idea how to use those conditional imports?
I tried self.arduino_display
and ArduinoDisplay.arduino_display
but to no avail.
I could obviously catch import errors, as in, add to the top:
try:
import arduino_display
except:
pass
...and that would fail on a RPI, which would be fine, but there must be a better way...
Upvotes: 5
Views: 537
Reputation: 50076
The problem is due to import
only binding names in its current scope. Doing an import
in a function/method does not make it available in other methods.
Perform the imports where you actually need them. For example, ArduinoDisplay
should import arduino_display
where it is used:
class ArduinoDisplay(DisplayBase):
# no new, no import
def __str__(self):
return "ArduinoDisplay"
def show(self, message):
# import where needed
import arduino_display
return arduino_display.println(message)
Note that import
is idempotent -- if the module has already been loaded before, import
just binds the name again. This makes such nested import
statements fast enough for most cases.
If your classes need many imports or speed is an issue, isolate classes into separate modules and import the entire module conditionally. You can directly assign the correct class using the common name, instead of having a dummy type that constructs another.
# ## display/arduino.py ##
# other systems accordingly
from .base import DisplayBase
# import once, globally
import arduino_display
class ArduinoDisplay(DisplayBase):
# no new, no import
def __str__(self):
return "ArduinoDisplay"
def show(self, message):
# import where needed
return arduino_display.println(message)
# ## display/__init__.py ##
from hardware import sysname
platform = sysname()
# resolve and import appropriate type once
if platform == "RaspberryPi":
from .rpi import RPIDisplay as Display
elif platform == "Arduino":
from .arduino import ArduinoDisplay as Display
else:
from .mock import MockDisplay as Display
Upvotes: 1
Reputation: 12154
The problem is that the function you import aren't saved anywhere for the other methods to see them, so they are not visible anywhere else but locally. Try setting ArduinoDisplay.arduino_display = arduino_display
after you import.
Here's some sample code that illustrates my meaning:
class ADisplay:
def __init__(self):
from builtins import print as display_write
def show(self, msg):
display_write(msg)
disp = ADisplay()
disp.show("mymsg")
This fails with NameError: name 'display_write' is not defined
.
Now, bind the import to your class.
class ADisplay:
def __init__(self):
from builtins import print as display_write
ADisplay.display_write = display_write
def show(self, msg):
self.display_write(msg)
disp = ADisplay()
disp.show("mymsg")
This works. In fact you can even dispense with show
by assigning it directly, if all those hardware print methods only take a string as a parameter and if you don't need to format or modify anything.
class ADisplay:
def __init__(self):
#do other init stuff...
pass
#only bind show the first time.
if getattr(ADisplay, "show", None):
return
from builtins import print as display_write
ADisplay.show = display_write
disp = ADisplay()
disp.show("mymsg")
Upvotes: 0
Reputation: 380
Have you tried using from arduino_display import println
then use println
just like you did with the RPIs writeline
?
Edit: missed the obvious...
You can do somthing like this:
from hardware import sysname
class Display(object):
def __init__(self, print_function, display_name):
self._print_function = print_function
self._display_name = display_name
def show(self, message):
self.print_function(message)
def __str__(self):
return self._display_name
def __repr__(self):
return self.__str__()
def create_display():
platform = sysname()
if platform == "RaspberryPi":
from rpi_display import writeline
return Display(writeline, "RPIDisplay")
elif platform == "Arduino":
from arduino_display import println
return Display(println, "ArduinoDisplay")
In case you need the classes and the nested factory, you can apply the same principle of storing the function object there too.
Upvotes: 0