stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
这将返回以下错误:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
我知道 eval
我们可以解决这个问题,但是没有更好更重要更安全的方法来计算存储在字符串中的数学表达式吗?
13条答案
按热度按时间2lpgd9681#
pyparsing可用于解析数学表达式。特别是,fourfn.py演示了如何解析基本算术表达式。下面,我将fourfn重写为一个数值解析器类,以便于重用。
你可以这样使用它
imzjd6km2#
埃瓦尔是邪恶的
注意:即使您使用set
__builtins__
到None
还是有可能通过内省爆发:使用ast计算算术表达式
您可以轻松地限制每个操作或任何中间结果的允许范围,例如,限制
a**b
:或限制中间结果的大小:
范例
soat7uwm3#
一些更安全的替代品
eval()
及sympy.sympify().evalf()
*:阿斯特瓦尔
numexpr
sympify
根据文件中的以下警告,也不安全。警告:请注意,此函数使用
eval
,因此不应在未初始化的输入上使用。ubby3x7f4#
好吧,eval的问题是它太容易逃出沙箱,即使你摆脱了它
__builtins__
. 所有逃离沙箱的方法都归结为使用getattr
或object.__getattribute__
(通过.
操作员)通过某个允许的对象获取对某个危险对象的引用(''.__class__.__bases__[0].__subclasses__
或类似)。getattr
通过设置来消除__builtins__
到None
.object.__getattribute__
这是一个困难的问题,因为它不能简单地被移除,因为object
是不可变的,因为删除它会破坏一切。然而,__getattribute__
只能通过.
运算符,以便从您的输入中清除足以确保eval无法逃离其沙箱。在处理公式时,小数的唯一有效用法是在其前面或后面加上
[0-9]
,所以我们只需删除.
.请注意,虽然python通常处理
1 + 1.
作为1 + 1.0
,这将删除尾部.
留给你1 + 1
. 你可以加上)
,,及
EOF
在接下来的事情清单上.
,但为什么要麻烦呢?ev7lccsx5#
您可以使用ast模块并编写nodevisitor,以验证每个节点的类型是否属于白名单的一部分。
因为它通过白名单而不是黑名单工作,所以它是安全的。它可以访问的唯一函数和变量是那些您显式授予它访问权限的函数和变量。我用数学相关函数填充了一个dict,因此如果需要,您可以轻松地提供对这些函数的访问,但您必须显式地使用它。
如果字符串试图调用尚未提供的函数,或调用任何方法,将引发异常,并且不会执行该异常。
因为它使用python的内置解析器和计算器,所以它还继承python的优先级和提升规则。
以上代码仅在Python3上进行了测试。
如果需要,可以在此函数上添加超时装饰器。
nbnkbykc6#
原因
eval
及exec
如此危险的是违约compile
函数将为任何有效的python表达式生成字节码,默认值为eval
或exec
将执行任何有效的python字节码。到目前为止,所有的答案都集中在限制可以生成的字节码(通过净化输入)或使用ast构建自己的领域特定语言。相反,您可以轻松创建一个简单的
eval
函数,该函数不能执行任何邪恶的操作,并且可以轻松地对所使用的内存或时间进行运行时检查。当然,如果是简单的数学,那就有捷径了。其工作方式很简单,任何常量数学表达式在编译期间都会安全地进行计算,并存储为常量。compile返回的代码对象包括
d
,这是的字节码LOAD_CONST
,后跟要加载的常量的编号(通常是列表中的最后一个),后跟S
,这是的字节码RETURN_VALUE
. 如果此快捷方式不起作用,则表示用户输入不是常量表达式(包含变量或函数调用或类似内容)。这也为一些更复杂的输入格式打开了大门。例如:
这需要实际评估字节码,这仍然很简单。python字节码是一种面向堆栈的语言,所以一切都是简单的操作
TOS=stack.pop(); op(TOS); stack.put(TOS)
或类似的。关键是只实现安全的操作码(加载/存储值、数学运算、返回值)和不安全的操作码(属性查找)。如果您希望用户能够调用函数(不使用上述快捷方式的全部原因),请简单地实现CALL_FUNCTION
仅允许“安全”列表中的函数。很明显,实际的版本会更长一些(有119个操作码,其中24个与数学有关)。添加
STORE_FAST
还有一些人会考虑像这样的输入'x=5;return x+x
或者类似的,非常容易。它甚至可以用来执行用户创建的函数,只要用户创建的函数本身是通过vmeval执行的(不要让它们可调用!!!或者它们可以被用作某个地方的回调)。处理循环需要支持goto
字节码,这意味着从for
迭代器while
和维护指向当前指令的指针,但并不太难。对于dos阻力,主循环应检查自开始计算以来经过了多少时间,某些操作员应拒绝输入超过某个合理限制(BINARY_POWER
最明显的)。虽然这种方法比简单表达式的简单语法解析器要长一些(参见上文关于获取编译常量的内容),但它很容易扩展到更复杂的输入,并且不需要处理语法(
compile
把任意复杂的东西简化成一系列简单的指令)。xbp102n07#
我想我会用
eval()
,但将首先检查以确保字符串是有效的数学表达式,而不是恶意的内容。您可以使用正则表达式进行验证。eval()
还接受其他参数,您可以使用这些参数限制它在其中操作的命名空间,以提高安全性。2ledvvac8#
这是一个非常晚的答复,但我认为有助于今后的参考。您可以使用sympy,而不是编写自己的数学解析器(尽管上面的pyparsing示例非常好)。我对它没有太多经验,但它包含的数学引擎比任何人为特定应用程序编写的引擎都强大得多,基本表达式求值非常简单:
真的很酷!A.
from sympy import *
引入了更多的功能支持,如trig函数、特殊函数等,但我在这里避免了这些,以说明从何而来。8oomwypt9#
[我知道这是一个老生常谈的问题,但值得指出新的有用解决方案]
自python3.6以来,这种功能现在被内置到语言中,即创造的“f-strings”。
参见:pep 498——文字字符串插值
例如(注意
f
前缀):u5rb5r5910#
基于perkins惊人的方法,我更新并改进了他的简单代数表达式的“快捷方式”(没有函数或变量)。现在它可以在python 3.6+上工作,并避免了一些陷阱:
测试,使用其他答案中的一些示例:
nzrxty8p11#
使用
eval
在干净的命名空间中:干净的命名空间应该防止注入。例如:
否则你会得到:
您可能希望授予访问数学模块的权限:
jfgube3f12#
这是我不使用eval解决问题的方法。使用python2和python3。它不适用于负数。
test.py
solution.py
du7egjpx13#
使用lark解析器库https://stackoverflow.com/posts/67491514/edit