Evaluating a mathematical expression in a string

前端 未结 11 1194
名媛妹妹
名媛妹妹 2020-11-21 05:01
stringExp = \"2^4\"
intVal = int(stringExp)      # Expected value: 16

This returns the following error:

Traceback (most recent call         


        
11条回答
  •  囚心锁ツ
    2020-11-21 05:13

    eval is evil

    eval("__import__('os').remove('important file')") # arbitrary commands
    eval("9**9**9**9**9**9**9**9", {'__builtins__': None}) # CPU, memory
    

    Note: even if you use set __builtins__ to None it still might be possible to break out using introspection:

    eval('(1).__class__.__bases__[0].__subclasses__()', {'__builtins__': None})
    

    Evaluate arithmetic expression using ast

    import ast
    import operator as op
    
    # 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}
    
    def eval_expr(expr):
        """
        >>> eval_expr('2^6')
        4
        >>> eval_expr('2**6')
        64
        >>> eval_expr('1 + 2*3**(4^5) / (6 + -7)')
        -5.0
        """
        return eval_(ast.parse(expr, mode='eval').body)
    
    def eval_(node):
        if isinstance(node, ast.Num): # 
            return node.n
        elif isinstance(node, ast.BinOp): #   
            return operators[type(node.op)](eval_(node.left), eval_(node.right))
        elif isinstance(node, ast.UnaryOp): #   e.g., -1
            return operators[type(node.op)](eval_(node.operand))
        else:
            raise TypeError(node)
    

    You can easily limit allowed range for each operation or any intermediate result, e.g., to limit input arguments for a**b:

    def power(a, b):
        if any(abs(n) > 100 for n in [a, b]):
            raise ValueError((a,b))
        return op.pow(a, b)
    operators[ast.Pow] = power
    

    Or to limit magnitude of intermediate results:

    import functools
    
    def limit(max_=None):
        """Return decorator that limits allowed returned values."""
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                ret = func(*args, **kwargs)
                try:
                    mag = abs(ret)
                except TypeError:
                    pass # not applicable
                else:
                    if mag > max_:
                        raise ValueError(ret)
                return ret
            return wrapper
        return decorator
    
    eval_ = limit(max_=10**100)(eval_)
    

    Example

    >>> evil = "__import__('os').remove('important file')"
    >>> eval_expr(evil) #doctest:+IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    TypeError:
    >>> eval_expr("9**9")
    387420489
    >>> eval_expr("9**9**9**9**9**9**9**9") #doctest:+IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    ValueError:
    

提交回复
热议问题