如何处理用户提供的公式?
我有一个字典,里面存了一些通过网页应用可以获取的键值对:
我想处理用户提供的公式,比如:
((value1+value3)/value4)*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 等对应到字典里的 value1,然后计算出结果呢?
谢谢!
3 个回答
0
在确认公式和数字的值都是正确的之后(比如通过正则表达式来检查),你可以做一些类似下面的操作:
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
感谢大家的意见。我个人觉得lacopo的回答最符合我的情况。
下面是解决方案的大致思路:
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)
6
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 的 数字表达式解析器 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 错误。