Question

How do you create effective reusable modules when

  1. one class in a module creates an instance of another class in another module, and
  2. the main program wants to use a method that is only in a subclass of one of the modules?

In the context of classes and subclasses, my goal is to:

  • Take the most general and reusable classes and extract them to modules that I can reuse across several programs.
  • Define subclasses in the main program to keep the most specific (and non-reusable) parts of the code outside of the modules.

The various classes have their own logic, tend to interact and create instances of each other within the modules -- all good. But when I need to add more specific subclassed methods to a class, those methods are not available to the instances created within the modules.

Here is an example of what I'm running across:

==firstclass.py===

"""This is a reusable class within a module.  It creates an instance of
another class in another module."""

from secondclass import Shape

class DataObject(object):
    """Create a class that holds data objects."""  
    def __init__(self, x, y):
        self.m_x = x
        self.m_y = y
        self.m_shape_list = []

    def createShape(self, type):
        # here we create an instance of the second class
        new_shape = Shape(type)
        self.m_shape_list.append(new_shape)

    def printCoords(self):
        print "Coordinates:", (x,y)

===secondclass.py===

"""This is another reusable class. An instance of this gets created within
an another class and it is also subclassed by the main program."""

class Shape(object):
    """Create a class that holds shape info."""
    def __init__(self,type):
        self.m_type = type
        print "Shape:",type

    def printShape(self):
        print "Shape:",self.m_type
===main.py===

"""This is my main program and where all the classes get subclassed to add
specific implementation details."""

from firstclass import DataObject
from secondclass import Shape

class GraphicObject(DataObject):
    """Create a subclass of DataObject that holds graphic specific info."""
    def __init__(self, x, y, color):
        print "Init MySubClass"
        super(GraphicObject,self).__init__(x, y)

    def createSomeShapes(self):
        self.createShape('circle')
        self.createShape('square')
        self.createShape('octogon')

    def renderAll(self):
        for shape in self.m_shape_list:
            shape.render()

class MyShape(Shape):
    """Create a subclass of Shape that holds graphic specific info."""
    def __init__(self, type):
        if type == circle:
            type = 'round thing'
        super(MyShape,self).__init__(type)

    def render(self):
        print "We got a",shape

# Create an instance of the first class
obj = GraphicObject(10, 10, 'yeller')

# Create a few instances of the second class through the interface 
#    provided by the first class
obj.createSomeShapes()

# Now attempts to call a method of the subclassed second class
obj.renderAll()

When I run this, I get:

$ python main.py
Init MySubClass
Shape: circle
Shape: square
Shape: octogon
Traceback (most recent call last):
  File "main.py", line 35, in <module>
    obj.renderAll()
  File "main.py", line 21, in renderAll
    shape.render()
AttributeError: 'Shape' object has no attribute 'render'

I know why it is happening, but I don't know a graceful way to avoid it.

What is the best practice here? How do you keep the module code reusable while giving access to subclassed methods?

Was it helpful?

Solution

This isn't really an issue about modules. You would have the same problem even if all the classes were in the same file. The problem is that your DataObject class is hard-coded to create an instance of Shape, and doesn't know that you have subclassed Shape and want to use that subclass instead. There are two ways around this:

  1. Document that subclasses must override createShape if they want to use their own custom Shape subclass instead of Shape. This is simple and effective. One downside is that it can be cumbersome to override the whole method if it is long and all you need to change is the name of the Shape class. On the other hand, if createShape also does other work that also usually needs to be overridden anyway in subclasses, then having to override it is not a big problem.
  2. Parameterize the DataObject/Shape relationship by giving your DataObject class a shapeClass class attribute, and use that to instantiate shapes instead of directly referencing Shape.

That is:

class DataObject(object):
    # other stuff...

    shapeClass = Shape

    def createShape(self, type):
        # here we create an instance of the second class
        new_shape = self.shapeClass(type)
        self.m_shape_list.append(new_shape)

Then you can override it more succinctly in the subclass:

class GraphicObject(DataObject):
    # other stuff...

    shapeClass = MyShape

By pulling Shape out of the method code and making it a separate attribute, you allow a subclass to override just that part of the behavior. Now calling someGraphicObject.createShape() will automatically create a MyShape.

Which approach is better depends on your overall class design, but the factors mentioned above (about whether methods using the paramterized class are likely to need to be overridden anyway in subclasses) are likely to be relevant.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top