我可以限制Python eval()结果范围吗?

时间:2017-04-19 18:00:36

标签: python eval limits

我可以限制eval()的值范围吗?

  1. eval('12345678**9')返回大量数字
  2. eval('1234567**8**9')既不返回也不抛出异常
  3. 我可以和#1一起生活,但不能和#2一起生活。我所需要的只是32位整数的范围。我担心没有办法告诉eval停止计算太大的数字,或者是否存在?

3 个答案:

答案 0 :(得分:1)

在使用ast将字符串解析为树然后走树之前,我写过“计算器”。在这种情况下,如果你想做一些诡计,你可以做到这一点:

import ast
import ctypes
import operator

def _pow(a, b):
    if isinstance(a, (ctypes.c_int, ctypes.c_float, ctypes.c_double)):
        a = float(a.value)
    if isinstance(b, (ctypes.c_int, ctypes.c_float, ctypes.c_double)):
        b = float(b.value)
    return ctypes.c_double(a ** b)

def _wrap_bin_op(op):
    def wrapper(a, b):
        if isinstance(a, (ctypes.c_int, ctypes.c_float, ctypes.c_double)):
            a = float(a.value)
        if isinstance(b, (ctypes.c_int, ctypes.c_float, ctypes.c_double)):
            b = float(b.value)
        return ctypes.c_double(op(a, b))
    return wrapper

def _wrap_unary_op(op):
    def wrapper(a):
        if isinstance(a, (ctypes.c_int, ctypes.c_float)):
            a = float(a.value)
        return ctypes.c_double(op(a))
    return wrapper


_OP_MAP = {
    ast.Add: _wrap_bin_op(operator.add),
    ast.Sub: _wrap_bin_op(operator.sub),
    ast.Pow: _wrap_bin_op(operator.pow),
    ast.Mult: _wrap_bin_op(operator.mul),
    ast.Div: _wrap_bin_op(operator.truediv),
    ast.Invert: _wrap_unary_op(operator.neg),
}


class Calc(ast.NodeVisitor):

    def visit_BinOp(self, node):
        left = self.visit(node.left)
        right = self.visit(node.right)
        return _OP_MAP[type(node.op)](left, right)

    def visit_Num(self, node):
        if isinstance(node.n, int):
            val = ctypes.c_int(node.n)
        elif isinstance(node.n, float):
            val = ctypes.c_double(node.n)
        return val

    def visit_Expr(self, node):
        return self.visit(node.value)

    @classmethod
    def evaluate(cls, expression):
        tree = ast.parse(expression)
        calc = cls()
        return calc.visit(tree.body[0])


print(Calc.evaluate('12345678**8'))
print(Calc.evaluate('5 * 8'))

请注意,与eval不同,我专门挑选并选择我想要允许的操作 - 我可以控制他们的行为方式。在这种情况下,我正在使用ctypes完成所有数学操作以避免巨大数字。我也在阻止整数__pow__并强制这些参数成为浮点数,然后再提高到特定的幂。

答案 1 :(得分:0)

正如John Coleman所建议的那样,我将在这里添加自己的解决方案。感谢您的讨论,我学到了很多关于蟒蛇能力的知识。

正如我已经评论过的那样:

我找到了一个解决办法,通过连接' .0',eval(' 1234567.0 ** 8.0 ** 9.0')抛出一个异常,即#39很好。

以下是嵌入此评估的更大背景:

 import itertools
 digits1to8 = list(str(i+1) for i in range(8)) #('1','2','3','4','5','6','7','8')

 with open("expressions.txt", "w") as outfile:
    for operators in itertools.product(['','.0+','.0-','.0*','.0/','.0**'], repeat=8):
       calculation = zip(digits1to8,operators)
       expression = (''.join(list(itertools.chain(*calculation))))+'9.0'
       try:
           out = str(eval(expression))+','
           expression = expression.replace('.0','')
           out = out.replace('.0,',',') + expression
           if (not out.find('.')>0):
               print(out, file=outfile)
       except:
           pass

事先我已经['' +#39;' - ',' *',' /',' **']而不是['',' .0 +',' .0 - &# 39;,' 0.0 *'' 0.0 /'' 0.0 **&#39]。总的来说,这仅仅是针对https://www.youtube.com/watch?v=-ruC5A9EzzE

的一些小数学实验

答案 2 :(得分:-1)

这些方面的东西:

from math import log

def bounded_eval(expression, bits = 32):
    nums = expression.split('**')
    if len(nums) == 1:
        val = eval(expression)
        if log(val,2) > bits:
            return "too large"
        else:
            return val
    else:
        base = nums[0]
        power = '**'.join(nums[1:])
        base = eval(base)
        power = eval(power)
        if power*log(base,2) > bits:
            return "too large"
        else:
            return pow(base,power)

这确实使用eval()这可能存在安全风险,但如果您只是在自己的代码生成的算术表达式上调用它,那么它实际上不是问题。显然,你可以用引起错误的代码替换返回“太大”的代码。