Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/362.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Python中解析用户提供的数学公式的安全方法_Python - Fatal编程技术网

在Python中解析用户提供的数学公式的安全方法

在Python中解析用户提供的数学公式的安全方法,python,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', 'p

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
但这是:

另外,使用
eval
解析和计算数学表达式对我来说似乎是错误的

我已经找到了,但它在引擎盖下也使用了
eval
,并且没有更好的效果:

>>> import MathParser
>>> m=MathParser.PyMathParser()
>>> m.expression = s_badbaduser
>>> m.evaluate();
Segmentation fault
是否有一个库可以在不使用Python解析器的情况下解析和计算数学表达式?

请检查。他编写了通用解析器和:

来自未来进口部的

将pyp解析导入为pyp
输入数学
进口经营者
类NumericStringParser(对象):
'''
大部分代码来自fourFn.py解析示例
http://pyparsing.wikispaces.com/file/view/fourFn.py
http://pyparsing.wikispaces.com/message/view/home/15549426
__作者保罗·麦奎尔
我所做的只是将Paul McGuire的fourFn.py重写为一个类,这样我就可以使用它了
在其他地方更容易。
'''
def推送优先(自身、strg、loc、toks):
self.exprStack.append(toks[0])
def pushUMinus(自身、strg、loc、toks):
如果toks和toks[0]='-':
self.exprStack.append('unary-')
定义初始化(自):
"""
expop::“^”
multop::“*”|“/”
addop::“+”|“-”
整数::['+'|'-']'0'..'9'+
原子::π| E | real | fn'('expr')'|'('expr')'
因子::原子[expop因子]*
术语::因子[多顶因子]*
expr::term[addop term]*
"""
point=pyp.Literal(“.”)
e=pyp.无案例横向(“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(“-”)
mult=pyp.Literal(“*”)
div=pyp.Literal(“/”)
lpar=pyp.Literal(“”.suppress()
rpar=pyp.Literal(“”)。suppress()
addop=正|负
multop=mult | div
expop=pyp.Literal(“^”)
pi=pyp.无案例横向(“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.pushumius)
#通过将指数定义为“原子[^factor]…”而不是
#“atom[^atom]…”,我们得到的是从右到左的指数,而不是从左到右的指数
#也就是说,2^3^2=2^(3^2),而不是(2^3)^2。
因子=pyp.Forward()
因子ε和((a>0)-(a<0))或0}
self.exprStack=[]
def评估测试包(自身):
op=s.pop()
如果op==“一元-”:
返回-自我评估测试包
如果在“+-*/^”中有op:
op2=自我评估测试包
op1=自我评估测试包
返回self.opn[op](op1,op2)
elif op==“PI”:
返回math.pi#3.1415926535
elif op==“E”:
返回math.e#2.718281828
elif op在self.fn中:
返回self.fn[op](self.evaluateStack)
elif op[0]。isalpha():
返回0
其他:
返回浮动(op)
def eval(self,num_字符串,parseAll=True):
self.exprStack=[]
结果=self.bnf.parseString(num_字符串,parseAll)
val=self.evaluateStack(self.exprStack[:])
返回值
nsp=NumericStringParser()
打印(nsp.eval('1+2'))
# 3.0
打印(nsp.eval('2*3-5'))
# 1.0
我建议使用解析树,然后将其列为白名单

tree = ast.parse(s, mode='eval')
valid = all(isinstance(node, whitelist) for node in ast.walk(tree))
if valid:
    result = eval(compile(tree, filename='', mode='eval'),
                  {"__builtins__": None}, safe_dict)
这里的白名单可以是:

whitelist = (ast.Expression, ast.Call, ast.Name, ast.Load,
             ast.BinOp, ast.UnaryOp, ast.operator, ast.unaryop, ast.cmpop,
             ast.Num,
            )

我在这里建立了几个帖子来创建一个评估类。也用 我基本上把它重写成一个类对象

import sys
import ast
import operator as op
import abc

import math

class IEvaluator:
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def eval_expr(cls, expr, subs):  # @NoSelf
        '''IMPORTANT: this is class method, overload it with @classmethod!
        Evaluate an expression given in the expr string.

        :param expr: str. String expression.
        :param subs: dict. Dictionary with values to substitute.
        :returns: Evaluated expression result.
        '''


class Evaluator(IEvaluator):
    '''Generic evaluator for a string expression. Uses ast and operator
    modules. The expr string is parsed with ast resulting in a node tree.
    Then the node tree is recursively traversed and evaluated with operations
    from the operator module.

    :implements: IEvaluator
    '''

    @classmethod
    def _get_op(cls, node):
        '''Get the operator corresponding to the node.
        :param node: Operator node type with node.op property.
        '''
        # supported operators
        operators = {
            ast.Add: op.add,
            ast.Sub: op.sub,
            ast.Mult: op.mul,
            ast.Div: op.truediv,
            ast.Pow: op.pow,
            ast.BitXor: op.xor,
            ast.USub: op.neg
        }
        return operators[type(node.op)]

    @classmethod
    def _get_op_fun(cls, node):
        # fun_call = {'sin': math.sin, 'cos': math.cos}[node.func.id]
        fun_call = getattr(math, node.func.id)
        return fun_call

    @classmethod
    def _num_op(cls, node, subs):
        '''Return the value of the node.
        :param node: Value node type with node.n property.
        '''
        return node.n

    @classmethod
    def _bin_op(cls, node, subs):
        '''Eval the left and right nodes, and call the binary operator.
        :param node: Binary operator with node.op, node.left, and node.right
            properties.
        '''
        op = cls._get_op(node)
        left_node = cls.eval(node.left, subs)
        right_node = cls.eval(node.right, subs)
        return op(left_node, right_node)

    @classmethod
    def _unary_op(cls, node, subs):
        '''Eval the node operand and call the unary operator.
        :param node: Unary operator with node.op and node.operand properties.
        '''
        op = cls._get_op(node)
        return op(cls.eval(node.operand, subs))

    @classmethod
    def _subs_op(cls, node, subs):
        '''Return the value of the variable represented by the node.
        :param node: Name node with node.id property to identify the variable.
        '''
        try:
            return subs[node.id]
        except KeyError:
            raise TypeError(node)

    @classmethod
    def _call_op(cls, node, subs):
        arg_list = []
        for node_arg in node.args:
            arg_list.append(cls.eval(node_arg, subs))
        fun_call = cls._get_op_fun(node)
        return fun_call(*arg_list)

    @classmethod
    def eval(cls, node, subs):
        '''The node is actually a tree. The node type i.e. type(node) is:
            ast.Num, ast.BinOp, ast.UnaryOp or ast.Name.
        Depending on the node type the node will have the following properties:
            node.n - Nodes value.
            node.id - Node id corresponding to a key in the subs dictionary.
            node.op - operation node. Type of node.op identifies the operation.
                type(node.op) is one of ast.Add, ast.Sub, ast.Mult, ast.Div,
                ast.Pow, ast.BitXor, or ast.USub.
            node.left or node.right - Binary operation node needs to have links
                to left and right nodes.
            node.operand - Unary operation node needs to have an operand.

        The binary and unary operations call eval recursively.
        '''
        # The functional logic is:
        # if isinstance(node, ast.Num):  # <number>
        #     return node.n
        # elif isinstance(node, ast.BinOp):  # <left> <operator> <right>
        #     return operators[type(node.op)](eval_(node.left, subs),
        #                                     eval_(node.right, subs))
        # elif isinstance(node, ast.UnaryOp):  # <operator> <operand> e.g., -1
        #     return operators[type(node.op)](eval_(node.operand, subs))
        # else:
        #     try:
        #         return subs[node.id]
        #     except KeyError:
        #         raise TypeError(node)

        node_type = type(node)

        return {
            # Value in the expression. Leaf.
            ast.Num: cls._num_op,  # <number>

            # Bin operation with two operands.
            ast.BinOp: cls._bin_op,  # <left> <operator> <right>

            # Unary operation such as neg.
            ast.UnaryOp: cls._unary_op,  # <operator> <operand> e.g., -1

            # Sub the value for the variable. Leaf.
            ast.Name: cls._subs_op,  # <variable>

            ast.Call: cls._call_op

        }[node_type](node, subs)

    @classmethod
    def eval_expr(cls, expr, subs=None):
        '''Evaluates a string expression. The expr string is parsed with ast
        resulting in a node tree. Then the eval method is used to recursively
        traverse and evaluate the nodes. Symbolic params are taken from subs.

        :Example:
            >>> eval_expr('2^6')
            4
            >>> eval_expr('2**6')
            64
            >>> eval_expr('1 + 2*3**(4^5) / (6 + -7)')
            -5.0
            >>> eval_expr('x + y', {'x': 1, 'y': 2})
            3

        :param expr: str. String expression.
        :param subs: dict. (default: globals of current and calling stack.)
        :returns: Result of running the evaluator.

        :implements: IEvaluator.eval_expr

        '''
        # ref: https://stackoverflow.com/a/9558001/3457624
        if subs is None:
            # Get the globals
            frame = sys._getframe()
            subs = {}
            subs.update(frame.f_globals)

            if frame.f_back:
                subs.update(frame.f_back.f_globals)

        expr_tree = ast.parse(expr, mode='eval').body
        return cls.eval(expr_tree, subs)
结果

2*x - y**2

(x, y): (1, 2)
2*x - y**2  =  -2

subs:  {'y': 2, 'x': 1}
2*x - y**2  =  -2

sin_subs:  {'y': 2, 'x': 1}
sin(log10(x*y)) =  0.296504042171

查看上的一个类似问题,我看到了这个问题,但执行用户提供的代码(无论如何保护)对我来说似乎不安全。上面的例子只是为了说明保护
eval
是极其困难的(如果可能的话)。我很希望有一个数学表达式解析器库。我更新了问题以反映这一点,谢谢。@user1202136:正是-数学表达式是用户提供的代码,所以我不想对它们求值()或以任何其他方式通过Python解析器运行它们。@PauloScardine:不,PyMathParser没有进行适当的清理(我测试过,与上面相同的问题-将更新问题)。我不是在试图重新发明轮子,这正是我问的原因。:)顺便说一句,获取
函数
代码
类型的一个更简单的方法是
(lambda:0)。\uuuu class\uuuuuuuuuuuuuuuuuu和
(lambda:0).func\u代码。\uuuuu class\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>分别感谢联合国大学,这看起来很完美!代码看起来不错!如何替换Python3中最好的
cmp(a,0)
呢?@riddlegical:为了Python3的兼容性,@SinanErdem:不限制数字的大小(比如说,如果操作仅仅是加法,那么可能会限制得太多),您可以在一段时间内运行
nsp.eval
。您可能还想知道进程可以使用什么。这部分代码直接来自。我猜是选择了将解析器弯曲到世界上大多数人使用的语法,而不是将世界上大多数人弯曲到Python使用的语法。这是一个很好的技巧!我仍然更喜欢unutbu的解决方案,因为它在设计上是安全的,但这是一个很好的技巧-我想这将使
eval
更加安全。谢谢这种方法无法防止像
9**9**9**9**9**9**9
这样的字符串试图
import sys
import ast
import operator as op
import abc

import math

class IEvaluator:
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def eval_expr(cls, expr, subs):  # @NoSelf
        '''IMPORTANT: this is class method, overload it with @classmethod!
        Evaluate an expression given in the expr string.

        :param expr: str. String expression.
        :param subs: dict. Dictionary with values to substitute.
        :returns: Evaluated expression result.
        '''


class Evaluator(IEvaluator):
    '''Generic evaluator for a string expression. Uses ast and operator
    modules. The expr string is parsed with ast resulting in a node tree.
    Then the node tree is recursively traversed and evaluated with operations
    from the operator module.

    :implements: IEvaluator
    '''

    @classmethod
    def _get_op(cls, node):
        '''Get the operator corresponding to the node.
        :param node: Operator node type with node.op property.
        '''
        # supported operators
        operators = {
            ast.Add: op.add,
            ast.Sub: op.sub,
            ast.Mult: op.mul,
            ast.Div: op.truediv,
            ast.Pow: op.pow,
            ast.BitXor: op.xor,
            ast.USub: op.neg
        }
        return operators[type(node.op)]

    @classmethod
    def _get_op_fun(cls, node):
        # fun_call = {'sin': math.sin, 'cos': math.cos}[node.func.id]
        fun_call = getattr(math, node.func.id)
        return fun_call

    @classmethod
    def _num_op(cls, node, subs):
        '''Return the value of the node.
        :param node: Value node type with node.n property.
        '''
        return node.n

    @classmethod
    def _bin_op(cls, node, subs):
        '''Eval the left and right nodes, and call the binary operator.
        :param node: Binary operator with node.op, node.left, and node.right
            properties.
        '''
        op = cls._get_op(node)
        left_node = cls.eval(node.left, subs)
        right_node = cls.eval(node.right, subs)
        return op(left_node, right_node)

    @classmethod
    def _unary_op(cls, node, subs):
        '''Eval the node operand and call the unary operator.
        :param node: Unary operator with node.op and node.operand properties.
        '''
        op = cls._get_op(node)
        return op(cls.eval(node.operand, subs))

    @classmethod
    def _subs_op(cls, node, subs):
        '''Return the value of the variable represented by the node.
        :param node: Name node with node.id property to identify the variable.
        '''
        try:
            return subs[node.id]
        except KeyError:
            raise TypeError(node)

    @classmethod
    def _call_op(cls, node, subs):
        arg_list = []
        for node_arg in node.args:
            arg_list.append(cls.eval(node_arg, subs))
        fun_call = cls._get_op_fun(node)
        return fun_call(*arg_list)

    @classmethod
    def eval(cls, node, subs):
        '''The node is actually a tree. The node type i.e. type(node) is:
            ast.Num, ast.BinOp, ast.UnaryOp or ast.Name.
        Depending on the node type the node will have the following properties:
            node.n - Nodes value.
            node.id - Node id corresponding to a key in the subs dictionary.
            node.op - operation node. Type of node.op identifies the operation.
                type(node.op) is one of ast.Add, ast.Sub, ast.Mult, ast.Div,
                ast.Pow, ast.BitXor, or ast.USub.
            node.left or node.right - Binary operation node needs to have links
                to left and right nodes.
            node.operand - Unary operation node needs to have an operand.

        The binary and unary operations call eval recursively.
        '''
        # The functional logic is:
        # if isinstance(node, ast.Num):  # <number>
        #     return node.n
        # elif isinstance(node, ast.BinOp):  # <left> <operator> <right>
        #     return operators[type(node.op)](eval_(node.left, subs),
        #                                     eval_(node.right, subs))
        # elif isinstance(node, ast.UnaryOp):  # <operator> <operand> e.g., -1
        #     return operators[type(node.op)](eval_(node.operand, subs))
        # else:
        #     try:
        #         return subs[node.id]
        #     except KeyError:
        #         raise TypeError(node)

        node_type = type(node)

        return {
            # Value in the expression. Leaf.
            ast.Num: cls._num_op,  # <number>

            # Bin operation with two operands.
            ast.BinOp: cls._bin_op,  # <left> <operator> <right>

            # Unary operation such as neg.
            ast.UnaryOp: cls._unary_op,  # <operator> <operand> e.g., -1

            # Sub the value for the variable. Leaf.
            ast.Name: cls._subs_op,  # <variable>

            ast.Call: cls._call_op

        }[node_type](node, subs)

    @classmethod
    def eval_expr(cls, expr, subs=None):
        '''Evaluates a string expression. The expr string is parsed with ast
        resulting in a node tree. Then the eval method is used to recursively
        traverse and evaluate the nodes. Symbolic params are taken from subs.

        :Example:
            >>> eval_expr('2^6')
            4
            >>> eval_expr('2**6')
            64
            >>> eval_expr('1 + 2*3**(4^5) / (6 + -7)')
            -5.0
            >>> eval_expr('x + y', {'x': 1, 'y': 2})
            3

        :param expr: str. String expression.
        :param subs: dict. (default: globals of current and calling stack.)
        :returns: Result of running the evaluator.

        :implements: IEvaluator.eval_expr

        '''
        # ref: https://stackoverflow.com/a/9558001/3457624
        if subs is None:
            # Get the globals
            frame = sys._getframe()
            subs = {}
            subs.update(frame.f_globals)

            if frame.f_back:
                subs.update(frame.f_back.f_globals)

        expr_tree = ast.parse(expr, mode='eval').body
        return cls.eval(expr_tree, subs)
import sympy

from eval_sympy import Evaluator

# test case...
x = sympy.Symbol('x')
y = sympy.Symbol('y')

expr = x * 2 - y ** 2
# z = expr.subs({x:1, y:2})

str_expr = str(expr)
print str_expr

x = 1
y = 2
out0 = Evaluator.eval_expr(str_expr)
print '(x, y): ({}, {})'.format(x, y)
print str_expr, ' = ', out0

subs1 = {'x': 1, 'y': 2}
out1 = Evaluator.eval_expr(str_expr, subs1)
print 'subs: ', subs1
print str_expr, ' = ', out1

sin_subs = {'x': 1, 'y': 2}
sin_out = Evaluator.eval_expr('sin(log10(x*y))', sin_subs)
print 'sin_subs: ', sin_subs
print 'sin(log10(x*y)) = ', sin_out
2*x - y**2

(x, y): (1, 2)
2*x - y**2  =  -2

subs:  {'y': 2, 'x': 1}
2*x - y**2  =  -2

sin_subs:  {'y': 2, 'x': 1}
sin(log10(x*y)) =  0.296504042171