Wrapping classes

Michael Spencer mahs at telcopartners.com
Fri Sep 23 20:03:13 EDT 2005


Jeremy Sanders wrote:
> Colin J. Williams wrote:
> 
> 
>>Could you not have functions a and b each of which returns a NumArray
>>instance?
>>
>>Your expression would then be something like a(..)+2*b(..).
> 
> 
> The user enters the expression (yes - I'm aware of the possible security
> issues), as it is a scientific application. I don't think they'd like to
> put () after each variable name.
> 
> I could always munge the expression after the user enters it, of course.
> 
> Jeremy
> 
Alternatively, you could build your own expression calculator, and initialize 
the objects if necessary as they are evaluated.  If you are happy with Python 
syntax for your expressiones then the stdlib compiler package is helpful.  The 
example below is not tested beyond what you see.  It's a bit verbose, but most 
of the code is boilerplate.

  >>> a = 3
  >>> b = 4
  >>> calc('a * b')
  using a
  using b
  12
  >>> calc('a * b ** (b - a) * "a"')
  using a
  using b
  using b
  using a
  'aaaaaaaaaaaa'
   >>> calc("0 and a or b")
  using b
  4
  >>> calc("1 and a or b")
  using a
  3
  >>> calc("1 and a or c")
  using a
  3
  >>> calc("0 and a or c")
  Undefined symbol: c
  >>>


HTH, Michael

-----------------

import compiler


class CalcError(Exception):
     def __init__(self,error,descr = None,node = None):
         self.error = error
         self.descr = descr
         self.node = node

     def __repr__(self):
         return "%s: %s" % (self.error, self.descr)
     __str__ = __repr__


class LazyCalc(object):

     def __init__(self, namespace):
         self._cache = {} # dispatch table
         self.context = namespace

     def visit(self, node,**kw):
         cls = node.__class__
         meth = self._cache.setdefault(cls,
             getattr(self,'visit'+cls.__name__,self.default))
         return meth(node, **kw)

     def visitExpression(self, node, **kw):
         return self.visit(node.node)


     # Binary Ops
     def visitAdd(self,node,**kw):
         return self.visit(node.left) + self.visit(node.right)
     def visitDiv(self,node,**kw):
         return self.visit(node.left) / self.visit(node.right)
     def visitFloorDiv(self,node,**kw):
         return self.visit(node.left) // self.visit(node.right)
     def visitLeftShift(self,node,**kw):
         return self.visit(node.left) << self.visit(node.right)
     def visitMod(self,node,**kw):
         return self.visit(node.left) % self.visit(node.right)
     def visitMul(self,node,**kw):
         return self.visit(node.left) * self.visit(node.right)
     def visitPower(self,node,**kw):
         return self.visit(node.left) ** self.visit(node.right)
     def visitRightShift(self,node,**kw):
         return self.visit(node.left) >> self.visit(node.right)
     def visitSub(self,node,**kw):
         return self.visit(node.left) - self.visit(node.right)

     # Unary ops
     def visitNot(self,node,*kw):
         return not self.visit(node.expr)
     def visitUnarySub(self,node,*kw):
         return -self.visit(node.expr)
     def visitInvert(self,node,*kw):
         return ~self.visit(node.expr)
     def visitUnaryAdd(self,node,*kw):
         return +self.visit(node.expr)

     # Flow Control
     def visitAnd(self,node,**kw):
         for arg in node.nodes:
             val = self.visit(arg)
             if not val:
                 return val
         return val
     def visitOr(self,node,**kw):
         for arg in node.nodes:
             val = self.visit(arg)
             if val:
                 return val
         return val

     # Logical Ops
     def visitBitand(self,node,**kw):
         return reduce(lambda a,b: a & b,[self.visit(arg) for arg in node.nodes])
     def visitBitor(self,node,**kw):
         return reduce(lambda a,b: a | b,[self.visit(arg) for arg in node.nodes])
     def visitBitxor(self,node,**kw):
         return reduce(lambda a,b: a ^ b,[self.visit(arg) for arg in node.nodes])
     def visitCompare(self,node,**kw):
         comparisons = {
             "<": operator.lt, # strictly less than
             "<=": operator.le,# less than or equal
             ">": operator.gt, # strictly greater than
             ">=": operator.ge, # greater than or equal
             "==": operator.eq, # equal
             "!=": operator.ne, # not equal
             "<>": operator.ne, # not equal
             "is": operator.is_, # object identity
             "is not": operator.is_not # negated object identity
             }
         obj = self.visit(node.expr)
         for op, compnode in node.ops:
             compobj = self.visit(compnode)
             if not comparisons[op](obj, compobj):
                 return False
             obj  = compobj
         return True


     # Values
     def visitCallFunc(self,node,**kw):
         raise CalcError("Functions not supported", node.node)

     def visitName(self, node, **kw):
         """LazyEvaluation"""
         name = node.name
         try:
             val = eval(name, self.context)
         except NameError:
             raise CalcError("Undefined symbol",name)
         except:
             raise
         print "using %s" % name # init if necessary here
         return val

     def visitConst(self, node, **kw):
         return node.value

     # Other
     def default(self, node, **kw):
         """Anything not expressly allowed is forbidden"""
         raise CalcError("Not Allowed",
                                 node.__class__.__name__,node)


def calc(source, context = None):
     walker = LazyCalc(context or globals())
     try:
         ast = compiler.parse(source,"eval")
     except SyntaxError, err:
         raise
     try:
         return walker.visit(ast)
     except CalcError, err:
         return err





More information about the Python-list mailing list