sagism
sagism

Reputation: 921

importing from within a python Abtract Factory

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

Answers (3)

MisterMiyagi
MisterMiyagi

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

JL Peyret
JL Peyret

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

schneebuzz
schneebuzz

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

Related Questions