By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
455,537 Members | 1,440 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 455,537 IT Pros & Developers. It's quick & easy.

Simple and safe evaluator

P: n/a

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.
Jun 27 '08 #1
Share this Question
Share on Google+
7 Replies


P: n/a
On Jun 11, 1:25 pm, bvdp <b...@mellowood.cawrote:
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
Jun 27 '08 #2

P: n/a
On Jun 11, 1:25 pm, bvdp <b...@mellowood.cawrote:
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

Expand|Select|Wrap|Line Numbers
  1. import _ast
  2.  
  3. class SafeEvalError(Exception):
  4. pass
  5.  
  6. class UnsafeCode(SafeEvalError):
  7. pass
  8.  
  9. # safe types:
  10. #   Sequences:
  11. #       list, tuple, dict, set, frozen_set*
  12. #   Literals: str, unicode, int, long, complex, float
  13. def safe_eval(text):
  14. "similar to eval, but only works on literals"
  15. ast = compile(text, "<string>", 'exec', _ast.PyCF_ONLY_AST)
  16. return _traverse(ast.body[0].value)
  17.  
  18. def _traverse(ast):
  19. if isinstance(ast, _ast.List):
  20. return [_traverse(el) for el in ast.elts]
  21. elif isinstance(ast, _ast.Tuple):
  22. return tuple(_traverse(el) for el in ast.elts)
  23. elif isinstance(ast, _ast.Dict):
  24. return dict(
  25. zip(
  26. (_traverse(k) for k in ast.keys),
  27. (_traverse(v) for v in ast.values)
  28. )
  29. )
  30. elif isinstance(ast, _ast.Str):
  31. return ast.s
  32. elif isinstance(ast, _ast.Num):
  33. return ast.n
  34. elif isinstance(ast, _ast.Expr):
  35. return _traverse(ast.value)
  36. elif isinstance(ast, _ast.BinOp):
  37. if isinstance(ast.op, _ast.Add):
  38. return _traverse(ast.left) + _traverse(ast.right)
  39. elif isinstance(ast.op, _ast.Sub):
  40. return _traverse(ast.left) - _traverse(ast.right)
  41. elif isinstance(ast.op, _ast.Div):
  42. return _traverse(ast.left) / _traverse(ast.right)
  43. elif isinstance(ast.op, _ast.FloorDiv):
  44. return _traverse(ast.left) // _traverse(ast.right)
  45. elif isinstance(ast.op, _ast.Mod):
  46. return _traverse(ast.left) % _traverse(ast.right)
  47. elif isinstance(ast.op, _ast.Mult):
  48. return _traverse(ast.left) * _traverse(ast.right)
  49. elif isinstance(ast.op, _ast.Pow):
  50. return _traverse(ast.left) ** _traverse(ast.right)
  51. elif isinstance(ast.op, _ast.BitAnd):
  52. return _traverse(ast.left) & _traverse(ast.right)
  53. elif isinstance(ast.op, _ast.BitOr):
  54. return _traverse(ast.left) | _traverse(ast.right)
  55. elif isinstance(ast.op, _ast.BitXor):
  56. return _traverse(ast.left) ^ _traverse(ast.right)
  57. elif isinstance(ast.op, _ast.LShift):
  58. return _traverse(ast.left) << _traverse(ast.right)
  59. elif isinstance(ast.op, _ast.RShift):
  60. return _traverse(ast.left) >_traverse(ast.right)
  61. elif isinstance(ast, _ast.BoolOp):
  62. if isinstance(ast.op, _ast.And):
  63. return all(_traverse(v) for v in ast.values)
  64. if isinstance(ast.op, _ast.Or):
  65. return any(_traverse(v) for v in ast.values)
  66. elif isinstance(ast, _ast.UnaryOp):
  67. if isinstance(ast.op, _ast.Invert):
  68. return _traverse(ast.operand)
  69. if isinstance(ast.op, _ast.USub):
  70. return -_traverse(ast.operand)
  71. if isinstance(ast.op, _ast.UAdd):
  72. return +_traverse(ast.operand)
  73. if isinstance(ast.op, _ast.Not):
  74. return not _traverse(ast.operand)
  75.  
  76.  
  77. raise UnsafeCode()
  78.  
  79. if __name__ == "__main__":
  80. print safe_eval("[1,2,3,{'hello':1}, (1,-2,3)], 4j, 1+5j, ~1+2*3")
  81.  
Jun 27 '08 #3

P: n/a
Matimus wrote:
On Jun 11, 1:25 pm, bvdp <b...@mellowood.cawrote:
>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

Expand|Select|Wrap|Line Numbers
  1. import _ast
  2. class SafeEvalError(Exception):
  3.     pass
  4. class UnsafeCode(SafeEvalError):
  5.     pass
  6. # safe types:
  7. #   Sequences:
  8. #       list, tuple, dict, set, frozen_set*
  9. #   Literals: str, unicode, int, long, complex, float
  10. def safe_eval(text):
  11.     "similar to eval, but only works on literals"
  12.     ast = compile(text, "<string>", 'exec', _ast.PyCF_ONLY_AST)
  13.     return _traverse(ast.body[0].value)
  14. def _traverse(ast):
  15.     if isinstance(ast, _ast.List):
  16.         return [_traverse(el) for el in ast.elts]
  17.     elif isinstance(ast, _ast.Tuple):
  18.         return tuple(_traverse(el) for el in ast.elts)
  19.     elif isinstance(ast, _ast.Dict):
  20.         return dict(
  21.                 zip(
  22.                     (_traverse(k) for k in ast.keys),
  23.                     (_traverse(v) for v in ast.values)
  24.                     )
  25.                 )
  26.     elif isinstance(ast, _ast.Str):
  27.         return ast.s
  28.     elif isinstance(ast, _ast.Num):
  29.         return ast.n
  30.     elif isinstance(ast, _ast.Expr):
  31.         return _traverse(ast.value)
  32.     elif isinstance(ast, _ast.BinOp):
  33.         if isinstance(ast.op, _ast.Add):
  34.             return _traverse(ast.left) + _traverse(ast.right)
  35.         elif isinstance(ast.op, _ast.Sub):
  36.             return _traverse(ast.left) - _traverse(ast.right)
  37.         elif isinstance(ast.op, _ast.Div):
  38.             return _traverse(ast.left) / _traverse(ast.right)
  39.         elif isinstance(ast.op, _ast.FloorDiv):
  40.             return _traverse(ast.left) // _traverse(ast.right)
  41.         elif isinstance(ast.op, _ast.Mod):
  42.             return _traverse(ast.left) % _traverse(ast.right)
  43.         elif isinstance(ast.op, _ast.Mult):
  44.             return _traverse(ast.left) * _traverse(ast.right)
  45.         elif isinstance(ast.op, _ast.Pow):
  46.             return _traverse(ast.left) ** _traverse(ast.right)
  47.         elif isinstance(ast.op, _ast.BitAnd):
  48.             return _traverse(ast.left) & _traverse(ast.right)
  49.         elif isinstance(ast.op, _ast.BitOr):
  50.             return _traverse(ast.left) | _traverse(ast.right)
  51.         elif isinstance(ast.op, _ast.BitXor):
  52.             return _traverse(ast.left) ^ _traverse(ast.right)
  53.         elif isinstance(ast.op, _ast.LShift):
  54.             return _traverse(ast.left) << _traverse(ast.right)
  55.         elif isinstance(ast.op, _ast.RShift):
  56.             return _traverse(ast.left) >_traverse(ast.right)
  57.     elif isinstance(ast, _ast.BoolOp):
  58.         if isinstance(ast.op, _ast.And):
  59.             return all(_traverse(v) for v in ast.values)
  60.         if isinstance(ast.op, _ast.Or):
  61.             return any(_traverse(v) for v in ast.values)
  62.     elif isinstance(ast, _ast.UnaryOp):
  63.         if isinstance(ast.op, _ast.Invert):
  64.             return _traverse(ast.operand)
  65.         if isinstance(ast.op, _ast.USub):
  66.             return -_traverse(ast.operand)
  67.         if isinstance(ast.op, _ast.UAdd):
  68.             return +_traverse(ast.operand)
  69.         if isinstance(ast.op, _ast.Not):
  70.             return not _traverse(ast.operand)
  71.     raise UnsafeCode()
  72. if __name__ == "__main__":
  73.     print safe_eval("[1,2,3,{'hello':1}, (1,-2,3)], 4j, 1+5j, ~1+2*3")
  74.  
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/Coo.../Recipe/286134

I was really hoping for a builtin on this :)

Thanks.
Jun 27 '08 #4

P: n/a
Simon Forman wrote:
On Jun 11, 1:25 pm, bvdp <b...@mellowood.cawrote:
>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
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.
Jun 27 '08 #5

P: n/a
On Jun 11, 3:25*pm, bvdp <b...@mellowood.cawrote:
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/spac...mage/fourFn.py.

-- Paul
Jun 27 '08 #6

P: n/a
bvdp 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?
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...
>>safe_eval("2+2")
4
>>safe_eval("4 * (8 / 3.1) ** 7.2")
3685.5618352828474
>>safe_eval("(2).__class__.__base__.__subclasses__ ()")
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.

--
Hans Nowak (zephyrfalcon at gmail dot com)
http://4.flowsnake.org/
Jun 27 '08 #7

P: n/a
On 2008-06-12, Hans Nowak <ze*******************@gmail.comwrote:
bvdp 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?

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:
It won't work for numbers expressed in scientific notation
(e.g. 1.23e-3).

--
Grant Edwards grante Yow! All right, you
at degenerates! I want
visi.com this place evacuated in
20 seconds!
Jun 27 '08 #8

This discussion thread is closed

Replies have been disabled for this discussion.