Adding an interface to existing classes

Spencer Pearson speeze.pearson at gmail.com
Thu Dec 22 03:21:41 EST 2011


I'm writing a geometry package, with Points and Lines and Circles and
so on, and eventually I want to be able to draw these things on the
screen. I have two options so far for how to accomplish this, but
neither of them sits quite right with me, and I'd like the opinion of
comp.lang.python's wizened elders.

Option 1. Subclassing.
The most Pythonic way would seem to be writing subclasses for the
things I want to display, adding a ".draw(...)" method to each one,
like this:
class DrawablePoint( geometry.Point ):
    class draw( self, ... ):
        ...

When the time comes to draw things, I'll have some list of objects I
want drawn, and say
for x in to_draw:
    x.draw(...)

I see a problem with this, though. The intersection of two lines is
(usually) an object of type Point. Since DrawableLine inherits from
Line, this means that unless I redefine the "intersect" method in
DrawableLine, the intersection of two DrawableLines will be a Point
object, not a DrawablePoint. I don't want to saddle the user with the
burden of converting every method output into its corresponding
Drawable subclass, and I don't want to redefine every method to return
an instance of said subclass. I see no other options if I continue
down the subclassing path. Am I missing something?




Option 2. A "draw" function, with a function dictionary.
This feels weird, but is fairly simple to write, use, and extend. We
have a module with a "draw_functions" dictionary that maps types onto
functions, and a "draw" function that just looks up the proper type in
the dictionary and calls the corresponding function. If you create
your own object, you can just add a new entry to the dictionary. The
implementation is simple enough to outline here:

In file "geometry/gui.py":
def draw_point(...):
    ...
def draw_line(...):
    ...
draw_functions = {geometry.Point: draw_point, geometry.Line:
draw_line, ...}
def draw( x, *args, **kwargs ):
    for type, callback in draw_functions.iteritems():
        if isinstance(x, type):
            callback(x, *args, **kwargs)
    else:
        raise TypeError("don't know how to draw things of type "
                        "{0}".format(type(x)))


In the file that uses this:
# Drawing a predefined type of object:
geometry.gui.draw(some_point, ...)
# Here we define a new kind of object and tell the package how to draw
it.
class MyObject(GeometricObject):
    ...
def draw_my_object(...):
    ...
geometry.gui.draw_functions[MyObject] = draw_my_object
# And now we draw it.
geometry.gui.draw(MyObject(...), ...)


If I feel fancy, I might use a decorator for adding entries to
draw_functions, but this is basically how it'd work.




The second way feels kludgey to me, but I'm leaning towards it because
it seems like so much less work and I'm out of ideas. Can anyone help,
explaining either a different way to do it or why one of these isn't
as bad as I think?

Thanks for your time!
-Spencer



More information about the Python-list mailing list