
本文介绍一种基于正则解析与操作符映射的安全、可扩展方案,用于批量校验字典中键值对是否同时满足用户输入的类 Python 表达式条件(如 'a > 5, 0本文介绍一种基于正则解析与操作符映射的安全、可扩展方案,用于批量校验字典中键值对是否同时满足用户输入的类 python 表达式条件(如 `'a > 5, 0
在实际数据处理场景中,常需根据用户动态输入的条件字符串(如 'a > 5, 0 绝不推荐。
以下提供一个轻量、健壮、无外部依赖(仅标准库)的解决方案,核心思想是:将条件字符串结构化解析 → 映射为安全的操作函数 → 按顺序组合求值。
✅ 核心实现逻辑
我们使用 re 进行词法切分,operator 模块提供标准比较函数,并通过 cast() 自动识别并转换字面量类型(整数、浮点、布尔、带引号字符串、None)。关键设计包括:
- 操作符预注册:支持 ==, !=, , >= 六种常见比较;
- 智能键值获取:若切分出的 token 是字典中存在的 key(如 'a'),则取 data['a'];否则尝试解析为字面量(如 '3.14' → 3.14, '"hello"' → 'hello');
- 链式比较支持:通过 v[i:i+2] 将 0
- 类型安全转换:cast() 函数严格匹配数字、布尔字面量('true'/'false')、单/双引号字符串及 None,拒绝非法输入。
? 完整可运行代码
import operator import re # 1. 定义支持的操作符及其对应函数 operators = { '<=': operator.le, '>=': operator.ge, '>': operator.gt, '<': operator.lt, '==': operator.eq, '!=': operator.ne } # 2. 构建正则:匹配任意已注册操作符(转义防特殊字符干扰) _oper = '|'.join(map(re.escape, operators.keys())) oper_re = re.compile(fr'\s*({_oper})\s*').split # 3. 数值与字符串匹配正则 number_re = re.compile(r'-?\d*(\.\d+)?').fullmatch string_re = re.compile(r'("|\')(?P<str>.*)\1').fullmatch def cast(value: str) -> float | int | bool | str | None: """安全解析字面量字符串为对应 Python 值""" v = value.strip() if not v: return None lower_v = v.lower() if lower_v in ('true', 'false'): return lower_v == 'true' if lower_v == 'none': return None if m := string_re(v): return m.group('str') if m := number_re(v): return float(v) if '.' in v else int(v) raise ValueError(f"无法解析字面量: {value}") def check_conditions(data: dict, conditions: str) -> bool: """ 判断字典 data 是否满足所有 conditions 中的条件 Args: data: 待校验的字典 conditions: 逗号分隔的条件字符串,如 'a > 5, 0 < (cd) < 6, ef == 35' Returns: bool: 所有条件均成立返回 True,否则 False Raises: ValueError: 条件语法错误(如操作符数量不匹配)或字面量无法解析 """ facts = [] # 存储每个原子比较的结果(True/False) for cond in conditions.split(','): cond = cond.strip() if not cond: continue values, ops = [], [] # 按操作符切分,交替提取值和操作符 tokens = oper_re(cond) for token in tokens: token = token.strip() if not token: continue if token in operators: ops.append(operators[token]) else: # 尝试从字典取值,失败则解析为字面量 val = data.get(token) if val is None: val = cast(token) values.append(val) # 验证结构:n 个操作符必须对应 n+1 个值(如 a<b<c → 2 op, 3 val) if len(values) != len(ops) + 1: raise ValueError(f"条件格式错误: '{cond}' —— 值与操作符数量不匹配") # 执行所有二元比较:v0 op0 v1, v1 op1 v2, ... for i in range(len(ops)): try: result = ops[i](values[i], values[i + 1]) except TypeError as e: raise TypeError(f"类型不匹配无法比较 '{values[i]}' {list(operators.keys())[list(operators.values()).index(ops[i])]} '{values[i+1]}'") from e facts.append(result) return all(facts) # ✅ 使用示例 if __name__ == "__main__": test_data = { '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, ef == 35', 'mn is None, ij == "hello", kl == False', 'ab < 5, gh > 10', ] for case in test_cases: try: result = check_conditions(test_data, case) print(f"✅ '{case}' → {result}") except Exception as e: print(f"❌ '{case}' → 错误: {e}")⚠️ 注意事项与最佳实践
- 安全性优先:本方案完全规避 eval() 和 exec(),所有用户输入均经显式解析与类型校验,杜绝远程代码执行(RCE)风险。
- 键名限制:字典键若含空格、括号、点号等(如 '(cd)'),需确保用户输入的条件中完全一致(包括括号),否则会当作字面量解析失败。
- 链式比较语义:0
- 扩展性提示:如需支持 in、not in、is、is not 等操作符,只需在 operators 字典中添加对应 operator 函数(如 operator.contains)并增强 cast() 对容器字面量的支持。
- 性能考量:对于高频调用场景,可将 oper_re、number_re 等正则编译为模块级常量,避免重复编译;复杂条件建议预编译为检查函数缓存。
该方案在简洁性、安全性与表达力之间取得良好平衡,适用于配置驱动型校验、低代码规则引擎、数据质量检查等生产环境,是替代 eval() 的专业级实践范本。

















