parallel class structures for AST-based objects

MRAB python at mrabarnett.plus.com
Sat Nov 21 19:07:05 EST 2009


Steve Howell wrote:
> I have been writing some code that parses a mini-language, and I am
> running into what I know is a pretty common design pattern problem,
> but I am wondering the most Pythonic way to solve it.
> 
> Basically, I have a bunch of really simple classes that work together
> to define an expression--in my oversimplified example code below,
> those are Integer, Sum, and Product.
> 
> Then I want to write different modules that work with those expression
> objects.  In my example, I have a parallel set of classes that enable
> you to evaluate an expression, and another set of objects that enable
> you to pretty-print the expression.
> 
> The below code works as intended so far (tested under 2.6), but before
> I go too much further with this design, I want to get a sanity check
> and some ideas on other ways to represent the interrelationships
> within the code.  Basically, the issue here is that you have varying
> behavior in two dimensions--a node right now is only a Product/Integer/
> Sum so far, but I might want to introduce new concepts like
> Difference, Quotient, etc.  And right now the only things that you can
> do to expressions is eval() them and pprint() them, but you eventually
> might want to operate on the expressions in new ways, including fairly
> abstract operations that go beyond a simple walking of the tree.
> 
> Here is the code:
> 
>     #######
>     # base classes just represents the expression itself, which
>     # get created by a parser or unit tests
>     # example usage is
>     # expression = Product(Sum(Integer(5),Integer(2)), Integer(6))
>     class Integer:
>         def __init__(self, val):
>             self.val = val
> 
>     class BinaryOp:
>         def __init__(self, a,b):
>             self.a = a
>             self.b = b
> 
>     class Sum(BinaryOp):
>         pass
> 
>     class Product(BinaryOp):
>         pass
> 
>     ########
> 
>     class EvalNode:
>         def __init__(self, node):
>             self.node = node
> 
>         def evaluatechild(self, child):
>             return EvalNode.factory(child).eval()
> 
>         @staticmethod
>         def factory(child):
>             mapper = {
>                 'Sum': SumEvalNode,
>                 'Product': ProductEvalNode,
>                 'Integer': IntegerEvalNode
>                 }
>             return abstract_factory(child, mapper)
> 
>     class SumEvalNode(EvalNode):
>         def eval(self):
>             a = self.evaluatechild(self.node.a)
>             b = self.evaluatechild(self.node.b)
>             return a + b
> 
>     class ProductEvalNode(EvalNode):
>         def eval(self):
>             a = self.evaluatechild(self.node.a)
>             b = self.evaluatechild(self.node.b)
>             return a * b
> 
>     class IntegerEvalNode(EvalNode):
>         def eval(self): return self.node.val
> 
>     #######
> 
>     class PrettyPrintNode:
>         def __init__(self, node):
>             self.node = node
> 
>         def pprint_child(self, child):
>             return PrettyPrintNode.factory(child).pprint()
> 
>         @staticmethod
>         def factory(child):
>             mapper = {
>                 'Sum': SumPrettyPrintNode,
>                 'Product': ProductPrettyPrintNode,
>                 'Integer': IntegerPrettyPrintNode
>                 }
>             return abstract_factory(child, mapper)
> 
>     class SumPrettyPrintNode(PrettyPrintNode):
>         def pprint(self):
>             a = self.pprint_child(self.node.a)
>             b = self.pprint_child(self.node.b)
>             return '(the sum of %s and %s)' % (a, b)
> 
>     class ProductPrettyPrintNode(PrettyPrintNode):
>         def pprint(self):
>             a = self.pprint_child(self.node.a)
>             b = self.pprint_child(self.node.b)
>             return '(the product of %s and %s)' % (a, b)
> 
>     class IntegerPrettyPrintNode(PrettyPrintNode):
>         def pprint(self): return self.node.val
> 
>     ##############
>     # Not sure where this method really "wants to be" structurally,
>     # or what it should be named, but it reduces some duplication
> 
>     def abstract_factory(node, node_class_mapper):
>         return node_class_mapper[node.__class__.__name__](node)
> 
> 
>     expression = Product(Sum(Integer(5),Integer(2)), Integer(6))
> 
>     evaluator = EvalNode.factory(expression)
>     print evaluator.eval()
> 
>     pprinter = PrettyPrintNode.factory(expression)
>     print pprinter.pprint()

I don't see the point of EvalNode and PrettyPrintNode. Why don't you
just give Integer, Sum and Product 'eval' and 'pprint' methods?



More information about the Python-list mailing list