Skip to main content
 首页 » 编程设计

python之如何处理用户提供的公式

2025年05月04日19think

我有一个字典,其中包含一组可通过网络应用程序获得的键值: 我想处理用户提供的公式,例如: ((值1+值3)/值4)*100

将公式计算出的匹配值与字典中的匹配值最简单的方法是什么?

考虑这个例子:

#!/usr/bin/python 
values={'value1':10,'value2':1245,'value3':674365,'value4':65432,'value5':131} 
formula=raw_input('Please enter formula:') 

如果我提供公式“((value1+value3)/value4)*100”,我如何将 value1 等映射到字典中的 map value1 并计算结果?

干杯,

请您参考如下方法:

eval 可用于执行恶意代码。

您信任您的用户吗?如果是这样,您可以将 values 作为全局字典传递给 eval 使用。因此,eval 可以直接计算用户公式而无需任何额外的字符串操作:

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

如果您不信任您的潜在用户,您可以使用 pyparsing: 以下是 Paul McGuire 的 numeric expression parser, fourFn.py ,包装在一个类中以便于使用。

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 

然后你的脚本可以做这样的事情:

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

这使用 re 模块将 my_dict["numberXXX"] 替换为 '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) 

下面是如何使用 NumericStringParser 来安全地评估数字表达式:

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

这比使用 eval 安全得多。所有无效的表达式都会引发 pyparsing.ParseException。