在 Python 中解析用户提供的数学公式的安全方法 [英] Safe way to parse user-supplied mathematical formula in Python

查看:34
本文介绍了在 Python 中解析用户提供的数学公式的安全方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有适用于 Python 的数学表达式解析器 + 评估器?

我不是第一个提出这个问题的人,但答案通常指向 eval().例如,可以这样做:

<预><代码>>>>safe_list = ['math','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'abs']>>>safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ])>>>s = "2+3">>>eval(s, {"__builtins__":None}, safe_dict)5

但这不安全:

<预><代码>>>>s_badbaduser = """... (lambda fc=(... 拉姆达 n: [... c 为 c 输入... ().__class__.__bases__[0].__subclasses__()...如果 c.__name__ == n...][0]……):... fc("函数")(... fc("代码")(... 0,0,0,0,"KABOOM",(),(),(),"","",0,""... ),{}... )()... )()……">>>评估(s_badbaduser,{__builtins__":无},safe_dict)分段故障

此外,使用 eval 解析和评估数学表达式对我来说似乎是错误的.

我发现了 PyMathParser,但它也在底层使用了 eval也好不到哪里去:

<预><代码>>>>导入 MathParser>>>m=MathParser.PyMathParser()>>>m.expression = s_badbaduser>>>m.evaluate();分段故障

是否有可用的库可以在不使用 Python 解析器的情况下解析和评估数学表达式?

解决方案

查看 Paul McGuire 的 pyparsing.他编写了通用解析器和算术表达式语法:

from __future__ 导入师将 pyparsing 导入为 pyp导入数学进口经营者类 NumericStringParser(object):'''大部分代码来自fourFn.py pyparsing示例http://pyparsing.wikispaces.com/file/view/fourFn.pyhttp://pyparsing.wikispaces.com/message/view/home/15549426__作者__='保罗麦奎尔'我所做的只是将 Paul McGuire 的fourFn.py 重新包装为一个类,这样我就可以使用它了在其他地方更容易.'''def pushFirst(self, strg, loc, toks ):self.exprStack.append(toks[0])def pushUMinus(self, strg, loc, toks ):如果 toks 和 toks[0] == '-':self.exprStack.append('一元-')def __init__(self):"""expop :: '^'multop :: '*' |'/'addop :: '+' |'-'整数 :: ['+' |'-'] '0'..'9'+原子:: PI |E |真实|fn '(' expr ')' |'('expr')'因子::原子[expop因子]*term :: 因子 [ 倍数因子 ]*expr :: term [ addop term ]*"""点 = pyp.Literal(".")e = pyp.CaselessLiteral("E")fnumber = pyp.Combine(pyp.Word("+-"+pyp.nums, pyp.nums) +pyp.Optional(point + pyp.Optional(pyp.Word(pyp.nums))) +pyp.Optional( e + pyp.Word( "+-"+pyp.nums, pyp.nums ) ) )ident = pyp.Word(pyp.alphas, pyp.alphas+pyp.nums+"_$")plus = pyp.Literal("+")减号 = pyp.Literal("-")多 = pyp.Literal("*")div = pyp.Literal("/")lpar = pyp.Literal( "(" ).suppress()rpar = pyp.Literal(")" ).suppress()addop = 加 |减multop = 多 |divexpop = pyp.Literal("^")pi = pyp.CaselessLiteral("PI")expr = pyp.Forward()原子 = ((pyp.Optional(pyp.oneOf("- +")) +(pi|e|fnumber|ident+lpar+expr+rpar).setParseAction(self.pushFirst))|pyp.Optional(pyp.oneOf("-+")) + pyp.Group(lpar+expr+rpar)).setParseAction(self.pushUMinus)# 通过将求幂定义为atom [ ^ factor ]..."而不是# "atom [ ^ atom ]...", 我们得到从右到左的指数,而不是从左到右# 即 2^3^2 = 2^(3^2),而不是 (2^3)^2.因子 = pyp.Forward()因数<<atom + pyp.ZeroOrMore( ( expop + factor ).setParseAction(self.pushFirst ) )term = factor + pyp.ZeroOrMore( ( multop + factor ).setParseAction(self.pushFirst ) )表达式<epsilon and ((a > 0) - (a <0)) or 0}self.exprStack = []defevaluateStack(self, s):op = s.pop()如果操作 == '一元 -':返回 -self.evaluateStack( s )如果操作在+-*/^"中:op2 = self.evaluateStack(s)op1 = self.evaluateStack(s)返回 self.opn[op]( op1, op2 )elif op ==PI":返回 math.pi # 3.1415926535elif op == "E":返回 math.e # 2.718281828self.fn 中的 elif 操作:返回 self.fn[op]( self.evaluateStack( s ) )elif op[0].isalpha():返回 0别的:返回浮点数(操作)def eval(self, num_string, parseAll = True):self.exprStack = []结果 = self.bnf.parseString(num_string, parseAll)val = self.evaluateStack( self.exprStack[:] )返回值nsp = NumericStringParser()打印(nsp.eval('1+2'))# 3.0打印(nsp.eval('2 * 3-5'))# 1.0

Is there a math expressions parser + evaluator for Python?

I am not the first to ask this question, but answers usually point to eval(). For instance, one could do this:

>>> safe_list = ['math','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'abs']
>>> safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ])
>>> s = "2+3"
>>> eval(s, {"__builtins__":None}, safe_dict)
5

But this is not safe:

>>> s_badbaduser = """
... (lambda fc=(
...     lambda n: [
...         c for c in 
...             ().__class__.__bases__[0].__subclasses__() 
...             if c.__name__ == n
...         ][0]
...     ):
...     fc("function")(
...         fc("code")(
...             0,0,0,0,"KABOOM",(),(),(),"","",0,""
...         ),{}
...     )()
... )()
... """
>>> eval(s_badbaduser, {"__builtins__":None}, safe_dict)
Segmentation fault

Also, using eval for parsing and evaluating mathematical expressions just seems wrong to me.

I have found PyMathParser, but it also uses eval under the hood and is no better:

>>> import MathParser
>>> m=MathParser.PyMathParser()
>>> m.expression = s_badbaduser
>>> m.evaluate();
Segmentation fault

Is there a library available that would parse and evaluate mathematical expression without using Python parser?

解决方案

Check out Paul McGuire's pyparsing. He has written both the general parser and a grammar for arithmetic expressions:

from __future__ import division
import pyparsing as pyp
import math
import operator

class NumericStringParser(object):
    '''
    Most of this code comes from the fourFn.py pyparsing example
    http://pyparsing.wikispaces.com/file/view/fourFn.py
    http://pyparsing.wikispaces.com/message/view/home/15549426
    __author__='Paul McGuire'

    All I've done is rewrap Paul McGuire's fourFn.py as a class, so I can use it
    more easily in other places.
    '''
    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 = pyp.Literal( "." )
        e     = pyp.CaselessLiteral( "E" )
        fnumber = pyp.Combine( pyp.Word( "+-"+pyp.nums, pyp.nums ) + 
                           pyp.Optional( point + pyp.Optional( pyp.Word( pyp.nums ) ) ) +
                           pyp.Optional( e + pyp.Word( "+-"+pyp.nums, pyp.nums ) ) )
        ident = pyp.Word(pyp.alphas, pyp.alphas+pyp.nums+"_$")       
        plus  = pyp.Literal( "+" )
        minus = pyp.Literal( "-" )
        mult  = pyp.Literal( "*" )
        div   = pyp.Literal( "/" )
        lpar  = pyp.Literal( "(" ).suppress()
        rpar  = pyp.Literal( ")" ).suppress()
        addop  = plus | minus
        multop = mult | div
        expop = pyp.Literal( "^" )
        pi    = pyp.CaselessLiteral( "PI" )
        expr = pyp.Forward()
        atom = ((pyp.Optional(pyp.oneOf("- +")) +
                 (pi|e|fnumber|ident+lpar+expr+rpar).setParseAction(self.pushFirst))
                | pyp.Optional(pyp.oneOf("- +")) + pyp.Group(lpar+expr+rpar)
                ).setParseAction(self.pushUMinus)       
        # by defining exponentiation as "atom [ ^ factor ]..." instead of 
        # "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
        # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
        factor = pyp.Forward()
        factor << atom + pyp.ZeroOrMore( ( expop + factor ).setParseAction(
            self.pushFirst ) )
        term = factor + pyp.ZeroOrMore( ( multop + factor ).setParseAction(
            self.pushFirst ) )
        expr << term + pyp.ZeroOrMore( ( addop + term ).setParseAction( self.pushFirst ) )
        self.bnf = expr
        # map operator symbols to corresponding arithmetic operations
        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,
                # For Python3 compatibility, cmp replaced by ((a > 0) - (a < 0)). See
                # https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
                "sgn" : lambda a: abs(a)>epsilon and ((a > 0) - (a < 0)) or 0}
        self.exprStack = []
    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

nsp = NumericStringParser()
print(nsp.eval('1+2'))
# 3.0

print(nsp.eval('2*3-5'))
# 1.0

这篇关于在 Python 中解析用户提供的数学公式的安全方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆