Turning String into Numerical Equation

Brian Kazian gtg856h at mail.gatech.edu
Sun Mar 13 18:08:54 CET 2005


Wow, thanks so much guys!


"Michael Spencer" <mahs at telcopartners.com> wrote in message 
news:mailman.340.1110697840.1799.python-list at python.org...
> Brian Kazian wrote:
>> Thanks for the help, I didn't even think of that.
>>
>> I'm guessing there's no easy way to handle exponents or logarithmic 
>> functions?  I will be running into these two types as well.
>> "Artie Gold" <artiegold at austin.rr.com> wrote in message 
>> news:39hrh2F61l1n2U1 at individual.net...
>>
>
> eval will handle exponents just fine: try eval("2**16")
> in fact, it will evaluate any legal python expression*
>
> logarithmic functions live in the math module, so you will either need to 
> import the functions/symbols you want from math, or give that namespace to 
> eval:
>
>  >>> import math
>  >>> eval("log(e)", vars(math))
>  1.0
>  >>>
>
> * this means that, eval("sys.exit()") will likely stop your interpreter, 
> and there are various other inputs with possibly harmful consequences.
>
> Concerns like these may send you back to your original idea of doing your 
> own expression parsing.  The good news is that the compiler package will 
> parse any legal Python expression, and return an Abstract Syntax Tree. 
> It's straightforward to walk the tree and achieve fine-grain control over 
> evaluation.
>
> Here's an example of a math calculator that doesn't use eval.  It 
> evaluates any Python scalar numeric expression (i.e., excludes container 
> types), and only those symbols and functions that are explicity specified. 
> This code is barely tested and probably not bullet-proof.  But with care 
> and testing it should be possible to achieve a good balance of 
> functionality and security.
>
>
> import compiler
> import types
> import math
>
> # create a namespace of useful funcs
> mathfuncs = {"abs":abs, "min": min, "max": max}
> mathfuncs.update((funcname, getattr(math,funcname)) for funcname in 
> vars(math)
>             if not funcname.startswith("_"))
>
> mathsymbols = {"pi":math.pi, "e":math.e}
>
> # define acceptable types - others will raise an exception if
> # entered as literals
> mathtypes = (int, float, long, complex)
>
> class CalcError(Exception):
>     def __init__(self,error,descr = None,node = None):
>         self.error = error
>         self.descr = descr
>         self.node = node
>         #self.lineno = getattr(node,"lineno",None)
>
>     def __repr__(self):
>         return "%s: %s" % (self.error, self.descr)
>     __str__ = __repr__
>
>
> class EvalCalc(object):
>
>     def __init__(self):
>         self._cache = {} # dispatch table
>
>     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)
>
>     def visitConst(self, node, **kw):
>         value = node.value
>         if isinstance(value, mathtypes):
>             return node.value
>         else:
>             raise CalcError("Not a numeric type", value)
>
>     # 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)
>
>     # Logical Ops
>     def visitAnd(self,node,**kw):
>         return reduce(lambda a,b: a and b,[self.visit(arg) for arg in 
> node.nodes])
>     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
>     def visitOr(self,node,**kw):
>         return reduce(lambda a,b: a or b,[self.visit(arg) for arg in 
> node.nodes])
>
>     def visitCallFunc(self,node,**kw):
>
>         func = self.visit(node.node, context = "Callable")
>         # Handle only positional args
>         posargs = [self.visit(arg) for arg in node.args]
>
>         return func(*posargs)
>
>     def visitName(self, node, context = None, **kw):
>         name = node.name
>         if context == "Callable":
>             # Lookup the function only in mathfuncs
>             try:
>                 return mathfuncs[name]
>             except KeyError:
>                 raise CalcError("Undefined function", name)
>         else:
>             try:
>                 return mathsymbols[name]
>             except KeyError:
>                 raise CalcError("Undefined symbol",name)
>
>     def default(self, node, **kw):
>         """Anything not expressly allowed is forbidden"""
>         raise CalcError("Syntax Error",
>                                 node.__class__.__name__,node)
>
>
> def calc(source):
>     walker = EvalCalc()
>     try:
>         ast = compiler.parse(source,"eval")
>     except SyntaxError, err:
>         raise
>     try:
>         return walker.visit(ast)
>     except CalcError, err:
>         return err
>
> Examples:
>
>  >>> calc("2+3*(4+5)*(7-3)**2")
>  434
>  >>> eval("2+3*(4+5)*(7-3)**2") # Check
>  434
>  >>> calc("sin(pi/2)")
>  1.0
>  >>> calc("sys.exit()")
>  Syntax Error: Getattr
>  >>> calc("0x1000 | 0x0100")
>  4352
>  >>>
>
>
>  Michael
>
>
>
> 





More information about the Python-list mailing list