How to process user supplied formulas?

前端 未结 3 471
孤城傲影
孤城傲影 2021-01-16 08:38

I have a dictionary containing a set of key values available through a web application: I want to process user supplied formulas like: ((value1+value3)/value4)*100

W

相关标签:
3条回答
  • 2021-01-16 08:59

    Thanks for all the input. I personally found that lacopo's answer suites my situation best.

    Here's a rough idea of the solution:

    import sys
    
    values={'one':10,'two':1245,'three':674365,'four':65432,'five':131}
    print str(values)
    
    formula=raw_input('Please enter formula:')
    
    for key, val in values.items():
            formula = formula.replace(key, str(val))
    
    whitelist=[ '+','-','/','*','^','(',')' ]
    
    to_evaluate=re.findall('\D',formula)
    to_evaluate=list(set(to_evaluate))
    
    for element in to_evaluate:
            if not element in whitelist:
                    print "Formula contains an invalid character: "+str(element)
                    sys.exit(1)
    
    
    print eval(formula)
    
    0 讨论(0)
  • 2021-01-16 09:13

    eval can be used execute malicious code.

    Do you trust your users? If so, you can pass values along as a global dict to be used by eval. Thus, eval can evaluate the user formula directly without any additional string manipulation:

    values={'value1':10,'value2':1245,'value3':674365,'value4':65432,'value5':131}
    formula=raw_input('Please enter formula:')
    values=eval(formula,values)
    print(values)
    

    If you do not trust your potential users, you could use pyparsing: The following is Paul McGuire's numeric expression parser, fourFn.py, wrapped in a class for easier use.

    utils_parse_numeric.py:

    from __future__ import division
    from pyparsing import (Literal,CaselessLiteral,Word,Combine,Group,Optional,
                           ZeroOrMore,Forward,nums,alphas,oneOf)
    import math
    import operator   
    
    class NumericStringParser(object):
        '''
        Most of this code comes from the fourFn.py pyparsing example
    
        '''
        def pushFirst(self, strg, loc, toks ):
            self.exprStack.append( toks[0] )
        def pushUMinus(self, strg, loc, toks ):
            if toks and toks[0]=='-': 
                self.exprStack.append( 'unary -' )
        def __init__(self):
            """
            expop   :: '^'
            multop  :: '*' | '/'
            addop   :: '+' | '-'
            integer :: ['+' | '-'] '0'..'9'+
            atom    :: PI | E | real | fn '(' expr ')' | '(' expr ')'
            factor  :: atom [ expop factor ]*
            term    :: factor [ multop factor ]*
            expr    :: term [ addop term ]*
            """
            point = Literal( "." )
            e     = CaselessLiteral( "E" )
            fnumber = Combine( Word( "+-"+nums, nums ) + 
                               Optional( point + Optional( Word( nums ) ) ) +
                               Optional( e + Word( "+-"+nums, nums ) ) )
            ident = Word(alphas, alphas+nums+"_$")       
            plus  = Literal( "+" )
            minus = Literal( "-" )
            mult  = Literal( "*" )
            div   = Literal( "/" )
            lpar  = Literal( "(" ).suppress()
            rpar  = Literal( ")" ).suppress()
            addop  = plus | minus
            multop = mult | div
            expop = Literal( "^" )
            pi    = CaselessLiteral( "PI" )
            expr = Forward()
            atom = ((Optional(oneOf("- +")) +
                     (pi|e|fnumber|ident+lpar+expr+rpar).setParseAction(self.pushFirst))
                    | Optional(oneOf("- +")) + Group(lpar+expr+rpar)
                    ).setParseAction(self.pushUMinus)       
            factor = Forward()
            factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( self.pushFirst ) )
            term = factor + ZeroOrMore( ( multop + factor ).setParseAction( self.pushFirst ) )
            expr << term + ZeroOrMore( ( addop + term ).setParseAction( self.pushFirst ) )
            self.bnf = expr
            epsilon = 1e-12
            self.opn = { "+" : operator.add,
                    "-" : operator.sub,
                    "*" : operator.mul,
                    "/" : operator.truediv,
                    "^" : operator.pow }
            self.fn  = { "sin" : math.sin,
                    "cos" : math.cos,
                    "tan" : math.tan,
                    "abs" : abs,
                    "trunc" : lambda a: int(a),
                    "round" : round,
                    "sgn" : lambda a: abs(a)>epsilon and cmp(a,0) or 0}
        def evaluateStack(self, s ):
            op = s.pop()
            if op == 'unary -':
                return -self.evaluateStack( s )
            if op in "+-*/^":
                op2 = self.evaluateStack( s )
                op1 = self.evaluateStack( s )
                return self.opn[op]( op1, op2 )
            elif op == "PI":
                return math.pi # 3.1415926535
            elif op == "E":
                return math.e  # 2.718281828
            elif op in self.fn:
                return self.fn[op]( self.evaluateStack( s ) )
            elif op[0].isalpha():
                return 0
            else:
                return float( op )
        def eval(self,num_string,parseAll=True):
            self.exprStack=[]
            results=self.bnf.parseString(num_string,parseAll)
            val=self.evaluateStack( self.exprStack[:] )
            return val
    

    Then your script could do something like this:

    import re
    import utils_parse_numeric as upn
    
    my_dict={
        'number1':54,
        'number2':1234,
        'number3':778,
        'number25':2109}
    

    This uses the re module to substitute my_dict["numberXXX"] for 'numberXXX':

    def callback(match):
        num=match.group(1)
        key='number{0}'.format(num)
        val=my_dict[key]
        return str(val)
    
    astr='((number1+number3)/number2)*100'
    astr=re.sub('number(\d+)',callback,astr)
    

    and here's how the NumericStringParser can be used to safely evaluate numeric expressions:

    nsp=upn.NumericStringParser()
    result=nsp.eval(astr)
    print(result)
    

    This is much safer than using eval. All invalid expressions will raise a pyparsing.ParseException.

    0 讨论(0)
  • 2021-01-16 09:16

    After validating both the formula and the numbers value (f.e. via a regexp) you can do something like:

    arr = {'num1':4, 'num2':5, 'num3':7}
    formula = '(num1+num2)*num3'
    
    for key, val in arr.items():
        formula = formula.replace(key, str(val))
    
    res = eval(formula)
    print res
    
    0 讨论(0)
提交回复
热议问题