Simple and safe evaluator

B

bvdp

Is there a simple/safe expression evaluator I can use in a python
program. I just want to pass along a string in the form "1 + 44 / 3" or
perhaps "1 + (-4.3*5)" and get a numeric result.

I can do this with eval() but I really don't want to subject my users to
the problems with that method.

In this use I don't need python to worry about complex numbers,
variables or anything else. Just do the math on a set of values. Would
eval() with some restricted list of permitted operators do the trick?

I'm feeling too lazy to write/debug my own parser for this :)

Thanks, Bob.
 
S

Simon Forman

Is there a simple/safe expression evaluator I can use in a python
program. I just want to pass along a string in the form "1 + 44 / 3" or
perhaps "1 + (-4.3*5)" and get a numeric result.

I can do this with eval() but I really don't want to subject my users to
the problems with that method.

In this use I don't need python to worry about complex numbers,
variables or anything else. Just do the math on a set of values. Would
eval() with some restricted list of permitted operators do the trick?

I'm feeling too lazy to write/debug my own parser for this :)

Thanks, Bob.



Funny, I need exactly the same kind of parser myself right now.
Fredrik Lundh has posted some code-and-explanation on an excellent
simple parser that's easy to extend. http://effbot.org/zone/simple-iterator-parser.htm

Just make it recognize the operator tokens you're interested in and if
a string parsers w/o errors then you know it's safe to eval().

I probably won't get to writing this myself for a few days or a week,
but if you do will you post it here (or send me a copy)? I'll do the
same if I get to it sooner.

Regards,
~Simon
 
M

Matimus

Is there a simple/safe expression evaluator I can use in a python
program. I just want to pass along a string in the form "1 + 44 / 3" or
perhaps "1 + (-4.3*5)" and get a numeric result.

I can do this with eval() but I really don't want to subject my users to
the problems with that method.

In this use I don't need python to worry about complex numbers,
variables or anything else. Just do the math on a set of values. Would
eval() with some restricted list of permitted operators do the trick?

I'm feeling too lazy to write/debug my own parser for this :)

Thanks, Bob.

Here is something that I wrote using the _ast module. It works pretty
well, and might be a good example for others wanting to experiment
with the _ast module. On a related note... if anybody wants to provide
feedback on this code it would be much appreciated. It involves a lot
of if/elif branches, and feels ugly.

Matt

Code:
import _ast

class SafeEvalError(Exception):
    pass

class UnsafeCode(SafeEvalError):
    pass

# safe types:
#   Sequences:
#       list, tuple, dict, set, frozen_set*
#   Literals: str, unicode, int, long, complex, float
def safe_eval(text):
    "similar to eval, but only works on literals"
    ast = compile(text, "<string>", 'exec', _ast.PyCF_ONLY_AST)
    return _traverse(ast.body[0].value)

def _traverse(ast):
    if isinstance(ast, _ast.List):
        return [_traverse(el) for el in ast.elts]
    elif isinstance(ast, _ast.Tuple):
        return tuple(_traverse(el) for el in ast.elts)
    elif isinstance(ast, _ast.Dict):
        return dict(
                zip(
                    (_traverse(k) for k in ast.keys),
                    (_traverse(v) for v in ast.values)
                    )
                )
    elif isinstance(ast, _ast.Str):
        return ast.s
    elif isinstance(ast, _ast.Num):
        return ast.n
    elif isinstance(ast, _ast.Expr):
        return _traverse(ast.value)
    elif isinstance(ast, _ast.BinOp):
        if isinstance(ast.op, _ast.Add):
            return _traverse(ast.left) + _traverse(ast.right)
        elif isinstance(ast.op, _ast.Sub):
            return _traverse(ast.left) - _traverse(ast.right)
        elif isinstance(ast.op, _ast.Div):
            return _traverse(ast.left) / _traverse(ast.right)
        elif isinstance(ast.op, _ast.FloorDiv):
            return _traverse(ast.left) // _traverse(ast.right)
        elif isinstance(ast.op, _ast.Mod):
            return _traverse(ast.left) % _traverse(ast.right)
        elif isinstance(ast.op, _ast.Mult):
            return _traverse(ast.left) * _traverse(ast.right)
        elif isinstance(ast.op, _ast.Pow):
            return _traverse(ast.left) ** _traverse(ast.right)
        elif isinstance(ast.op, _ast.BitAnd):
            return _traverse(ast.left) & _traverse(ast.right)
        elif isinstance(ast.op, _ast.BitOr):
            return _traverse(ast.left) | _traverse(ast.right)
        elif isinstance(ast.op, _ast.BitXor):
            return _traverse(ast.left) ^ _traverse(ast.right)
        elif isinstance(ast.op, _ast.LShift):
            return _traverse(ast.left) << _traverse(ast.right)
        elif isinstance(ast.op, _ast.RShift):
            return _traverse(ast.left) >> _traverse(ast.right)
    elif isinstance(ast, _ast.BoolOp):
        if isinstance(ast.op, _ast.And):
            return all(_traverse(v) for v in ast.values)
        if isinstance(ast.op, _ast.Or):
            return any(_traverse(v) for v in ast.values)
    elif isinstance(ast, _ast.UnaryOp):
        if isinstance(ast.op, _ast.Invert):
            return _traverse(ast.operand)
        if isinstance(ast.op, _ast.USub):
            return -_traverse(ast.operand)
        if isinstance(ast.op, _ast.UAdd):
            return +_traverse(ast.operand)
        if isinstance(ast.op, _ast.Not):
            return not _traverse(ast.operand)


    raise UnsafeCode()

if __name__ == "__main__":
    print safe_eval("[1,2,3,{'hello':1}, (1,-2,3)], 4j, 1+5j, ~1+2*3")
 
B

bvdp

Matimus said:
Is there a simple/safe expression evaluator I can use in a python
program. I just want to pass along a string in the form "1 + 44 / 3" or
perhaps "1 + (-4.3*5)" and get a numeric result.

I can do this with eval() but I really don't want to subject my users to
the problems with that method.

In this use I don't need python to worry about complex numbers,
variables or anything else. Just do the math on a set of values. Would
eval() with some restricted list of permitted operators do the trick?

I'm feeling too lazy to write/debug my own parser for this :)

Thanks, Bob.

Here is something that I wrote using the _ast module. It works pretty
well, and might be a good example for others wanting to experiment
with the _ast module. On a related note... if anybody wants to provide
feedback on this code it would be much appreciated. It involves a lot
of if/elif branches, and feels ugly.

Matt

Code:
import _ast

class SafeEvalError(Exception):
pass

class UnsafeCode(SafeEvalError):
pass

# safe types:
#   Sequences:
#       list, tuple, dict, set, frozen_set*
#   Literals: str, unicode, int, long, complex, float
def safe_eval(text):
"similar to eval, but only works on literals"
ast = compile(text, "<string>", 'exec', _ast.PyCF_ONLY_AST)
return _traverse(ast.body[0].value)

def _traverse(ast):
if isinstance(ast, _ast.List):
return [_traverse(el) for el in ast.elts]
elif isinstance(ast, _ast.Tuple):
return tuple(_traverse(el) for el in ast.elts)
elif isinstance(ast, _ast.Dict):
return dict(
zip(
(_traverse(k) for k in ast.keys),
(_traverse(v) for v in ast.values)
)
)
elif isinstance(ast, _ast.Str):
return ast.s
elif isinstance(ast, _ast.Num):
return ast.n
elif isinstance(ast, _ast.Expr):
return _traverse(ast.value)
elif isinstance(ast, _ast.BinOp):
if isinstance(ast.op, _ast.Add):
return _traverse(ast.left) + _traverse(ast.right)
elif isinstance(ast.op, _ast.Sub):
return _traverse(ast.left) - _traverse(ast.right)
elif isinstance(ast.op, _ast.Div):
return _traverse(ast.left) / _traverse(ast.right)
elif isinstance(ast.op, _ast.FloorDiv):
return _traverse(ast.left) // _traverse(ast.right)
elif isinstance(ast.op, _ast.Mod):
return _traverse(ast.left) % _traverse(ast.right)
elif isinstance(ast.op, _ast.Mult):
return _traverse(ast.left) * _traverse(ast.right)
elif isinstance(ast.op, _ast.Pow):
return _traverse(ast.left) ** _traverse(ast.right)
elif isinstance(ast.op, _ast.BitAnd):
return _traverse(ast.left) & _traverse(ast.right)
elif isinstance(ast.op, _ast.BitOr):
return _traverse(ast.left) | _traverse(ast.right)
elif isinstance(ast.op, _ast.BitXor):
return _traverse(ast.left) ^ _traverse(ast.right)
elif isinstance(ast.op, _ast.LShift):
return _traverse(ast.left) << _traverse(ast.right)
elif isinstance(ast.op, _ast.RShift):
return _traverse(ast.left) >> _traverse(ast.right)
elif isinstance(ast, _ast.BoolOp):
if isinstance(ast.op, _ast.And):
return all(_traverse(v) for v in ast.values)
if isinstance(ast.op, _ast.Or):
return any(_traverse(v) for v in ast.values)
elif isinstance(ast, _ast.UnaryOp):
if isinstance(ast.op, _ast.Invert):
return _traverse(ast.operand)
if isinstance(ast.op, _ast.USub):
return -_traverse(ast.operand)
if isinstance(ast.op, _ast.UAdd):
return +_traverse(ast.operand)
if isinstance(ast.op, _ast.Not):
return not _traverse(ast.operand)


raise UnsafeCode()

if __name__ == "__main__":
print safe_eval("[1,2,3,{'hello':1}, (1,-2,3)], 4j, 1+5j, ~1+2*3")

Oh, this is interesting. Similar to some other code I found on the web
which grabs a list of permitted token values using the dis module:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286134

I was really hoping for a builtin on this :)

Thanks.
 
B

bvdp

Simon said:
Funny, I need exactly the same kind of parser myself right now.
Fredrik Lundh has posted some code-and-explanation on an excellent
simple parser that's easy to extend. http://effbot.org/zone/simple-iterator-parser.htm

Just make it recognize the operator tokens you're interested in and if
a string parsers w/o errors then you know it's safe to eval().

I probably won't get to writing this myself for a few days or a week,
but if you do will you post it here (or send me a copy)? I'll do the
same if I get to it sooner.

Regards,
~Simon

I'll have to read Fredrik's code a few more times, but I think it makes
as much sense as anything else. Of course, I could take the lazy man's
way out and just to a left->right evaluation without any ()s, etc.,
which in my project would work. But, honestly, I thought it'd be easier.
I was going to use eval() until I realized that it was not a good idea.
Darn shame we have to work so hard to prevent some jerk's malicious code
from effecting our stuff. Oh well, that's life.
 
P

Paul McGuire

Is there a simple/safe expression evaluator I can use in a python
program. I just want to pass along a string in the form "1 + 44 / 3" or
perhaps "1 + (-4.3*5)" and get a numeric result.

I can do this with eval() but I really don't want to subject my users to
the problems with that method.

In this use I don't need python to worry about complex numbers,
variables or anything else. Just do the math on a set of values. Would
eval() with some restricted list of permitted operators do the trick?

I'm feeling too lazy to write/debug my own parser for this :)

Thanks, Bob.

This example ships with pyparsing, and can be extended to support
built-in functions: http://pyparsing.wikispaces.com/space/showimage/fourFn.py.

-- Paul
 
H

Hans Nowak

bvdp said:
Is there a simple/safe expression evaluator I can use in a python
program. I just want to pass along a string in the form "1 + 44 / 3" or
perhaps "1 + (-4.3*5)" and get a numeric result.

I can do this with eval() but I really don't want to subject my users to
the problems with that method.

In this use I don't need python to worry about complex numbers,
variables or anything else. Just do the math on a set of values. Would
eval() with some restricted list of permitted operators do the trick?

This solution may be overly simply (especially compared to the AST-based
solution suggested earlier), but... if all you need is numbers and operators,
*maybe* you can get away with stripping all letters from the input string (and
possibly the underscore), and then evaluating it:


import re
import traceback

re_letters = re.compile("[a-zA-Z_]+")

def safe_eval(s):
s = re_letters.sub("", s)
return eval(s)

# try it out...
4
3685.5618352828474
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "safe_eval.py", line 12, in safe_eval
return eval(s)
File "<string>", line 1
(2)...()
^
SyntaxError: invalid syntax

....It's primitive, but it might work for your purposes.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top