[Tutor] subclassing across multiple modules

Kent Johnson kent37 at tds.net
Fri Mar 18 02:44:09 CET 2005


Brian van den Broek wrote:
> A schematic of what I have (with fake names for ease of example) is a 
> base module Toolkit.py and I want to write a module Application.py which 
> specializes the behaviour of the Toolkit.py classes. (I'm using 
> old-style classes, but don't feel committed to that choice.)
> 
> Toolkit.py defines:
> 
> class Tree
> class Node
> class Node1(Node)
> class Node2(Node)
> (other Node subclasses, too, but I'm simplifying.)
> 
> The Tree class contains a parser method to parse a file. It reads the 
> file, breaking it into chunks, and for each chunk, does the following:
> 
> if some_condition_on_chunk_contents:
>     node = Node1(chunk_contents)
> else:
>     node = Node2(chunk_contents)
> 
> self.nodes.append(node)
> 
> 
> Application.py will define
> 
> class Tree(Toolkit.Tree)
> class Node(Toolkit.Node)
> class Node1(Node, Toolkit.Node1)
> class Node2(Node, Toolkit.Node2)

You're asking for trouble using the same name. Do something like
class MyTree(Toolkit.Tree)
class MyNode(Toolkit.Node)
class MyNode1(MyNode, Toolkit.Node1)
class MyNode2(MyNode, Toolkit.Node2)

The multiple inheritance from MyNode and Toolkit.NodeX is a smell. I guess you do this because you 
want to override methods of Toolkit.Node as well as Toolkit.NodeX, or add methods to both MyNode1 
and MyNode2? I would look for another way to do this, maybe using some kind of helper class to hold 
some common functions?

> In all cases, a few methods will be added. I had no plans to override 
> existing methods.
> 
> My problem is that I want Application.Tree.parser to create 
> Application.Node1 and Application.Node2 instances when parsing a file. 
>  From testing around with simpler cases, it seems as though unless I 
> override Toolkit.Tree.parser in Application.Tree, the inherited 
> Tree.parser method will create Toolkit.Node1 and Node2 objects, which 
> isn't what I want.
> 
> Toolkit.Tree.parser is my longest Tree method, and I definitely think it 
> would be bad to just copy and paste the method def into Application.Tree 
> so make the node = Node1(), etc. lines create an Application.Node1, etc. 
> object.

Right, you don't want to do this.

> The best I have come up with is to replace the Toolkit.Tree.parser lines 
> of the form:
>     node = Node1(chunk_contents)
> with lines like
>     node = self.get_Node1(chunk_contents)
> 
> and then have *both* Toolkit.Tree and Application.Tree define methods:
> 
> def get_Node1(self, chunk_contents):
>     return Node1(chunk_contents)
> 
> (like lines and methods for Node2)
> 
> This works in my test case, but still involves Copy and Paste of the 
> identical methods get_Node1 (and get_Node2) in both modules. Smelly. :-P

This is actually good design. It is a simple example of the Template Method pattern. Use Template 
Method when you have a function that defines the outline of an algorithm, but you need to specialize 
some part of the algorithm. You wrap the function in a class (you already have this since it is a 
method) and call helper methods for the parts you want to specialize. Then subclasses override the 
helper methods.

The methods in MyTree will be different from the ones in Toolkit.Tree, they will look like
def get_Node1(self, chunk_contents):
     return MyNode1(chunk_contents)


A couple of alternatives:

You could actually pass the class constructors directly to Tree.parser(). It would look like this:

def parser(self, makeNode1, makeNode2):  # makeNode1 & 2 are callables that return nodes
   # ...
   if some_condition_on_chunk_contents:
       node = makeNode1(chunk_contents)
   else:
       node = makeNode2(chunk_contents)

Then at the point of call you have
   tree.parser(MyNode1, MyNode2)  # constructors are callable

There are several variations on this depending on the details of your requirements. You could pass 
makeNode1 and makeNode2 to the Tree constructor and save them as instance variables. You could have 
a separate class with makeNode1() and makeNode2() methods and pass an instance of that to parse() or 
Tree.__init__()...

Kent



More information about the Tutor mailing list