Simple and safe evaluator

bvdp bob at mellowood.ca
Wed Jun 11 17:49:57 EDT 2008


Matimus wrote:
> On Jun 11, 1:25 pm, bvdp <b... at mellowood.ca> wrote:
>> 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")
> [/code]

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.



More information about the Python-list mailing list