How do I convert arithemtic string (like "2+2") to a number?

Michael Spencer mahs at telcopartners.com
Sun Feb 6 12:13:50 EST 2005


John J. Lee wrote:
> "Adomas" <adomas.paltanavicius at gmail.com> writes:
> 
> 
>>Well, a bit more secure would be
>>
>>eval(expression, {'__builtins__': {}}, {})
>>
>>or alike.
> 
> 
> Don't believe this without (or even with ;-) very careful thought,
> anyone.  Google for rexec.
> 
> 
> John
This module provides a more systematic way to set up restricted evaluation:


"""Restricted evaluation

     Main entry point: r_eval()
     For usage see class tests or run them using testall()"""


import types
import compiler
import operator

import sys, os # used only for testing

ast = compiler.ast

class Eval_Error(Exception):
     def __init__(self,error,descr = None,node = None):
         self.error = error
         self.descr = descr

     def __repr__(self):
         return "%s: %s" % (self.error, self.descr)
     __str__ = __repr__


class AbstractVisitor(object):
     """Standard depth-first AST walker - dispatches to methods
         based on Node class name"""
     def __init__(self):
         self._cache = {} # dispatch table

     def visit(self, node,**kw):
         if node is None: return None
         cls = node.__class__
         meth = self._cache.setdefault(cls,
             getattr(self,'visit'+cls.__name__,self.default))
         return meth(node, **kw)

     def default(self, node, **kw):
         for child in node.getChildNodes():
             return self.visit(child, **kw)
     visitExpression = default



class Eval(AbstractVisitor):
     """An AST walker that implements a replacement to built-in eval.

     See r_eval for entry point/usage.

     Provides hooks for managing name resolution, proxying objects,
     and controlling attribute access

     Does not implement:
         List Comprehensions, Generator Expressions, Lambda
         Ellipsis (can this be used without numpy?)
     """
     def __init__(self, context = globals()):
         super(Eval,self).__init__()
         self.context = context

     # Namespace interface.  Safe implementations should override these methods
     # to implement restricted evaluation.  This implementation simply
     # evals the name in self.context and provides no proxying or
     # attribute lookup restrictions

     def lookup(self, objname):
         """Called only by visitName.  Raise an exception here
         to prevent any direct name resolution, but note that
         objects may be returned by callables or attribute lookups"""
         return eval(objname, self.context)

     def getObject(self, obj):
         """Called by all name resolvers and by CallFunc.  Provides
         a hook for proxying unsafe objects"""
         return obj

     def getAttribute(self,obj,attrname):
         """Called by visitGetattr"""
         return getattr(obj,attrname)

     # End Namespace interface


     # Syntax nodes follow by topic group.  Delete methods to disallow
     # certain syntax.

     # Object constructor nodes
     def visitConst(self, node, **kw):
         return node.value
     def visitDict(self,node,**kw):
         return dict([(self.visit(k),self.visit(v)) for k,v in node.items])
     def visitTuple(self,node, **kw):
         return tuple(self.visit(i) for i in node.nodes)
     def visitList(self,node, **kw):
         return [self.visit(i) for i in node.nodes]
     def visitSliceobj(self,node,**kw):
         return slice(*[self.visit(i) for i in node.nodes])
     def visitEllipsis(self,node,**kw):
         raise NotImplementedError, "Ellipsis"

     # 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])


     # Name resolution
     def visitGetattr(self,node,**kw):
         obj = self.visit(node.expr)
         return self.getAttribute(obj,node.attrname)

     def visitName(self,node, **kw):
         return self.lookup(node.name)

     def visitSlice(self,node,**kw):
         obj = self.visit(node.expr)
         objslice = obj[self.visit(node.lower):self.visit(node.upper)]
         return self.getObject(objslice)

     def visitSubscript(self,node,**kw):
         obj = self.visit(node.expr)
         subs = node.subs
         if len(subs) > 1:
             raise NotImplementedError, "Subscript must be integer or slice"
         assert node.flags == "OP_APPLY"
         return self.getObject(operator.getitem(obj,self.visit(subs[0])))


     # Callables
     def visitCallFunc(self,node,**kw):
         func = self.visit(node.node)

         args = node.args
         kwargs = self.visit(node.dstar_args) or {}
         posargs = []
         for arg in node.args:
             if isinstance(arg, ast.Keyword):
                 keyword, value = self.visit(arg)
                 if keyword in kwargs:
                     raise TypeError, "%s() got multiple values for keyword 
argument '%s'" \
                             % (func,keyword)
                 kwargs[keyword] = value
             else:
                 posargs.append(self.visit(arg))
         posargs.extend(node.star_args or [])

         return self.getObject(func(*posargs,**kwargs))

     def visitKeyword(self,node,**kw):
         return node.name, self.visit(node.expr)

     # Miscellaneous

     def visitBackquote(self, node, **kw):
         return repr(self.visit(node.expr))

     # Function/class definition

     def visitLambda(self, node, **kw):
         raise NotImplementedError, "Lambda"

     # Iterator evaluations

     def visitGenExpr(self,node,*kw):
         raise NotImplemetedError, "GenExpr"
     def visitListComp(self,node,*kw):
         raise NotImplemetedError, "ListComp"

     # Some unexpected node type
     def default(self, node, **kw):
         raise Eval_Error("Unsupported source construct",
                                 node.__class__,node)




def r_eval(source, context = None):
     """eval partial replacement,

         Does not implement:
         List Comprehensions, Generator Expressions, Lambda. Ellipsis

         """

     walker = Eval(context or globals())
     try:
         ast = compiler.parse(source,"eval")
     except SyntaxError, err:
         raise
     try:
         return walker.visit(ast)
     except Eval_Error, err:
         raise


class tests(object):

     def run(self):
         """Run all the tests"""
         for name in dir(self):
             if name.startswith("test_"):
                 getattr(self,name)()
                 print "%s: OK" % name

     def test_const(self):
         cases = ["""[1, 2, 'Joe Smith', 8237972883334L,   # comment
       {'Favorite fruits': ['apple', 'banana', 'pear']},  # another comment
       'xyzzy', [3, 5, [3.14159, 2.71828, []]]]"""]
         for case in cases:
             assert eval(case) == r_eval(case)

     def test_algebra(self):
         """Show that r_eval matches eval on constant expressions"""
         cases = [
             "1+2/3 * 4.0 ** 5 % 2",
             "(4 << 2 | 67 >> 2) ^ 0xFF",
             ]
         for case in cases:
             assert eval(case) == r_eval(case)
     def test_names(self):
         cases = [
             "sum",
             "sys.modules['os']",
             ]
         for case in cases:
             assert eval(case) == r_eval(case)

     def test_calling(self):
         cases = [
             "getattr(object, 'subclasses'.join(['_'*2]*2))",
             "type('Name', dict = {'a':1}, bases = (object,)).a",
             "type(**dict(dict = {'a':1}, name = 'Name', bases = (object,))).a"
             ]
         for case in cases:
             assert eval(case) == r_eval(case)


def testall():
     tests().run()




More information about the Python-list mailing list