我可以限制 Python eval() 结果范围吗?
Can I limit the Python eval() result range?
我可以限制eval()的取值范围吗?
eval('12345678**9')
returns一个很大的数字
eval('1234567**8**9')
既不returns也不抛出异常
我可以接受#1 但不能接受#2。我需要的只是 32 位整数范围内的结果。我担心没有办法告诉 eval 停止计算太大的数字,或者有吗?
大致如下:
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()
,这可能存在安全风险,但如果您只是在您自己的代码生成的算术表达式上调用它,那么这并不是真正的问题。显然,您可以将 returns "too large" 的代码替换为引发错误的代码。
在使用ast
将字符串解析成树然后遍历树之前,我已经写了"calculators"。在这种情况下,如果你想耍点小把戏,你可以这样做:
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
做我所有的数学运算以避免 HUGE 数字。我还阻止整数 __pow__
并强制这些参数在提升到特定幂之前变成浮点数。
按照 John Coleman 的建议,我将在此处添加我自己的解决方案。感谢大家的讨论,学到了很多pythons的能力。
正如我已经评论过的:
我找到了一个解决方案,通过连接 '.0' 使任何数字成为浮点数,eval('1234567.0**8.0**9.0') 抛出异常,没关系。
这是更大的上下文,其中嵌入了此评估:
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
之前我有 ['','+','-','*','/','**'] 而不是 ['','.0+','.0-' ,'.0*','.0/','.0**']。总的来说,这只是响应 https://www.youtube.com/watch?v=-ruC5A9EzzE
的一个小数学实验
我可以限制eval()的取值范围吗?
eval('12345678**9')
returns一个很大的数字eval('1234567**8**9')
既不returns也不抛出异常
我可以接受#1 但不能接受#2。我需要的只是 32 位整数范围内的结果。我担心没有办法告诉 eval 停止计算太大的数字,或者有吗?
大致如下:
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()
,这可能存在安全风险,但如果您只是在您自己的代码生成的算术表达式上调用它,那么这并不是真正的问题。显然,您可以将 returns "too large" 的代码替换为引发错误的代码。
在使用ast
将字符串解析成树然后遍历树之前,我已经写了"calculators"。在这种情况下,如果你想耍点小把戏,你可以这样做:
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
做我所有的数学运算以避免 HUGE 数字。我还阻止整数 __pow__
并强制这些参数在提升到特定幂之前变成浮点数。
按照 John Coleman 的建议,我将在此处添加我自己的解决方案。感谢大家的讨论,学到了很多pythons的能力。
正如我已经评论过的:
我找到了一个解决方案,通过连接 '.0' 使任何数字成为浮点数,eval('1234567.0**8.0**9.0') 抛出异常,没关系。
这是更大的上下文,其中嵌入了此评估:
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
之前我有 ['','+','-','*','/','**'] 而不是 ['','.0+','.0-' ,'.0*','.0/','.0**']。总的来说,这只是响应 https://www.youtube.com/watch?v=-ruC5A9EzzE
的一个小数学实验