Reputation: 2136
How do you create effective reusable modules when
In the context of classes and subclasses, my goal is to:
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?
Upvotes: 0
Views: 117
Reputation: 251438
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:
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.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.
Upvotes: 3