Simple and safe evaluator

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • bvdp

    Simple and safe evaluator


    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.
  • Simon Forman

    #2
    Re: Simple and safe evaluator

    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

    Comment

    • Matimus

      #3
      Re: Simple and safe evaluator

      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

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

      Comment

      • bvdp

        #4
        Re: Simple and safe evaluator

        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
        >
        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:


        I was really hoping for a builtin on this :)

        Thanks.

        Comment

        • bvdp

          #5
          Re: Simple and safe evaluator

          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.

          Comment

          • Paul McGuire

            #6
            Re: Simple and safe evaluator

            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

            Comment

            • Hans Nowak

              #7
              Re: Simple and safe evaluator

              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.5618352828 474
              >>safe_eval("(2 ).__class__.__b ase__.__subclas ses__()")
              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)

              Comment

              • Grant Edwards

                #8
                Re: Simple and safe evaluator

                On 2008-06-12, Hans Nowak <zephyrfalcon!N O_SPAM!@gmail.c omwrote:
                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!

                Comment

                Working...