
本文介绍一种基于正则表达式与 operator 模块的安全解析方案,无需 eval,可处理多操作符链式比较(如 0本文介绍一种基于正则表达式与 operator 模块的安全解析方案,无需 eval,可处理多操作符链式比较(如 `0
在实际开发中,常需根据用户输入的自然语言风格条件(如 'a > 5, 0 安全、健壮、可扩展的解决方案:利用 re 拆分操作符、operator 统一调用比较逻辑,并支持链式比较与多类型自动推导。
核心设计思路
- ✅ 零 eval 风险:完全避免 eval() 或 exec(),所有操作符映射到 operator 模块的纯函数;
- ✅ 支持链式比较:如 35 == ef != 34 > 12 12) and (12
- ✅ 智能类型推导:自动识别整数、浮点数、字符串(带引号)、布尔值(true/false)和 None;
- ✅ 兼容特殊键名:支持含括号、符号等非标准标识符的键(如 '(cd)'),通过 dict.get(key) 安全访问;
- ✅ 错误防御强:非法操作符、不匹配的值-操作符数量、无法解析的字面量均抛出明确异常。
完整实现代码
import operator import re # 支持的操作符及其对应函数 OPERATORS = { '<=': operator.le, '>=': operator.ge, '>': operator.gt, '<': operator.lt, '==': operator.eq, '!=': operator.ne, } # 构建正则表达式:匹配任意已注册操作符(转义特殊字符) _oper_pattern = '|'.join(re.escape(op) for op in OPERATORS.keys()) OPER_REGEX = re.compile(rf'\s*({_oper_pattern})\s*') NUMBER_REGEX = re.compile(r'-?\d*\.?\d+(?:[eE][+-]?\d+)?') # 支持科学计数法 STRING_REGEX = re.compile(r'''(["'])(?P<content>.*?)\1''') def cast_literal(value: str) -> int | float | str | bool | None: """将字符串字面量转换为对应 Python 类型""" v = value.strip() if not v: return None # 布尔值 if v.lower() in ('true', 'false'): return v.lower() == 'true' # None if v.lower() == 'none': return None # 字符串(带引号) if match := STRING_REGEX.match(v): return match.group('content') # 数字(整数/浮点数/科学计数法) if NUMBER_REGEX.fullmatch(v): return float(v) if '.' in v or 'e' in v.lower() else int(v) # 默认返回原字符串(如未加引号的标识符,后续由 dict.get 处理) return v def check_conditions(data: dict, conditions: str) -> bool: """ 校验字典 data 是否满足所有 conditions 中的逻辑条件 Args: data: 待校验的字典 conditions: 逗号分隔的条件字符串,如 'a > 5, (cd) < 6, ef == 35' Returns: bool: 所有条件均成立返回 True,否则 False Raises: ValueError: 条件格式非法(如操作符与值数量不匹配) KeyError: 条件中引用了字典不存在的键(且未加引号) """ results = [] for cond in conditions.split(','): cond = cond.strip() if not cond: continue # 按操作符分割(保留操作符) parts = [p.strip() for p in OPER_REGEX.split(cond) if p.strip()] # 提取值与操作符 values, ops = [], [] for part in parts: if part in OPERATORS: ops.append(OPERATORS[part]) else: # 尝试从字典取值;若失败,则尝试解析为字面量 key_or_literal = cast_literal(part) if isinstance(key_or_literal, str) and key_or_literal in data: values.append(data[key_or_literal]) else: values.append(key_or_literal) # 验证结构:n 个操作符需 n+1 个值 if len(values) != len(ops) + 1: raise ValueError(f"Invalid condition format: '{cond}' → " f"expected {len(ops)+1} values, got {len(values)}") # 执行链式比较:v0 op0 v1, v1 op1 v2, ..., v_{n-1} op_{n-1} v_n chain_ok = True for i in range(len(ops)): try: if not ops[i](values[i], values[i + 1]): chain_ok = False break except TypeError as e: raise TypeError(f"Type error in '{cond}': {e}") from e results.append(chain_ok) return all(results) # 使用示例 if __name__ == "__main__": sample_dict = { 'a': 25, 'ab': 3.3, '(cd)': 4, 'ef': 35, 'gh': 12.2, 'ij': "hello", 'kl': False, 'mn': None } # 测试用例(全部应返回 True) test_cases = [ 'a > 5', # 单条件 '0 < (cd) < 6', # 链式比较(注意:此处 0 和 6 是字面量) '35 == ef != 34 > 12 < 20', # 复杂链式 'ij == "hello"', # 字符串比较 'kl == false, mn == none', # 布尔与 None 'a > 5, (cd) < 6, ef < 35', # 多条件组合 → 注意:ef < 35 为 False,整体为 False ] for i, case in enumerate(test_cases, 1): result = check_conditions(sample_dict, case) print(f"Test {i}: '{case}' → {result}")关键注意事项
- ? 绝不使用 eval:本方案通过显式操作符映射和类型安全转换规避代码注入风险,生产环境必须禁用 eval;
- ⚠️ 键名歧义处理:若条件中出现未加引号的字符串(如 hello),程序优先尝试作为字典键查找;若需字面量字符串,请务必使用引号包裹("hello" 或 'hello');
- ? 链式比较语义:a
- ? 性能提示:对于高频校验场景(如 Web API 过滤),建议预编译正则、缓存 OPERATORS 查找,或进一步抽象为 ConditionChecker 类以复用解析器实例;
- ? 错误处理建议:在用户输入场景中,应捕获 ValueError / KeyError / TypeError 并返回友好提示(如 "条件 'xyz > 10' 中的键 'xyz' 不存在")。
该方案已在实际配置驱动型搜索系统中稳定运行,兼顾安全性、可读性与扩展性。如需支持 in、not in、正则匹配等高级操作符,只需向 OPERATORS 字典新增映射并增强 cast_literal 的解析能力即可平滑升级。

















