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" <[EMAIL PROTECTED]> wrote in message news:[EMAIL PROTECTED]
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
-- http://mail.python.org/mailman/listinfo/python-list