simpleeval:给 Python 写个安全的表达式计算器

danthedeckie/simpleeval 是一个轻量的 Python 库,斩获了 603 个 Star。

正文顶部截图

README区域截图

simpleeval 的核心功能是安全的表达式求值。当应用中需要让用户输入数学公式或简单逻辑表达式时,直接用 eval() 风险太高,自己写解析器又太麻烦。simpleeval 提供了一个折中方案。

它基于 Python 内置的 ast 模块解析表达式,能够精确控制哪些运算被允许、哪些被禁止。从底层杜绝了执行危险代码的可能。ast 模块将表达式解析为抽象语法树,simpleeval 在遍历这棵树时只执行白名单内的节点类型,遇到未知节点直接抛异常。


安装使用

通过 pip 安装:

pip install simpleeval

也可以直接把源文件复制到项目中。整个库只有一个文件,没有复杂的依赖关系,可以随时按需修改。


基本用法

最简用法:

from simpleeval import simple_eval

simple_eval("21 + 21")

返回 42。支持更复杂的表达式:

simple_eval("21 + 19 / 7 + (8 % 3) ** 9")

也可以传入自定义函数:

simple_eval("square(11)", functions={"square": lambda x: x*x})

如果需要执行大量求值操作,可以创建 SimpleEval 对象反复使用,避免重复解析的开销:

s = SimpleEval()
s.eval("1 + 1")
s.eval("100 * 10")

SimpleEval 对象还支持预先解析表达式并缓存语法树,之后在不同 names 下反复求值。这对于需要批量计算的场景很有帮助。


支持的运算

默认支持的运算符包括四则运算、比较运算、位运算以及 in 判断。如果不习惯 ^ 的位运算语义,可以把它替换为乘方运算。** 运算符的幂次上限默认为 4000000,防止构造出耗时过长的表达式。

表达式也支持 Python 的三元写法:

simple_eval("'equal' if x == y else 'not equal'",
            names={"x": 1, "y": 2})

安全机制

simpleeval 有多层防护。字符串长度上限为 100000 字符。列表或字符串通过乘法疯狂扩展时会被拦截。

对象属性访问方面,以下划线或 func_ 开头的属性默认禁止访问。如果需要更严格的控制,可以传入 allowed_attrs 参数,将模式从黑名单切换为白名单,只放行明确允许的属性。

如果需要在表达式中暴露模块(如 os.path 的部分功能),可以使用 ModuleWrapper 进行包装,只开放指定的方法:

s = SimpleEval(names={
    'path': ModuleWrapper(os.path, allowed_attrs={'exists', 'join'})
})

扩展性

SimpleEval 类可以通过继承来定制行为。例如禁用在对象上调用方法,或者添加新的运算规则。对于需要支持 dict、list、tuple 等复合类型字面量的场景,可以直接使用 EvalWithCompoundTypes 类,它也支持简单的列表推导式,并配有最大推导长度限制防止滥用。


注意事项

simpleeval 的安全性仅针对传入的表达式本身,依赖 Python 解释器正常运作。如果传递了经过 monkey-patch 的对象或包含危险代码的函数,它无法提供保护。项目作者也提醒,许多安全研究者认为在 CPython 中做沙箱化几乎不可能,使用者需要自行评估风险。不过对于常规的表达式求值场景,simpleeval 提供的安全边界已经足够。


适用场景

适合在 Web 应用中让用户输入简单公式、设置告警规则中的条件判断、或在配置文件中使用动态表达式。一个常见的例子是根据时段和音乐播放状态计算闹钟音量。另外一个典型用法是在电子表格类应用中查询单元格数据。

simpleeval 的设计初衷就是保持简单,没有堆积大量功能,但覆盖了日常开发中安全求值的大多数需求。项目自带测试套件,代码风格通过 black、isort、pylint 和 mypy 检查,维护质量有保障。

simpleeval 的设计初衷就是保持简单,没有堆积大量功能,但覆盖了日常开发中安全求值的大多数需求。项目自带测试套件,代码风格通过 black、isort、pylint 和 mypy 检查,维护质量有保障。

更多推荐