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" <[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

Reply via email to