本文介绍一种基于正则解析与 operator 模块的安全、可扩展方案,用于将用户输入的类 Python 条件字符串(如 'a > 5, 0 < (cd) < 6')动态映射为对字典的实际逻辑校验,避免 eval() 风险,支持链式比较、多种数据类型及嵌套操作。
本文介绍一种基于正则解析与 operator 模块的安全、可扩展方案,用于将用户输入的类 python 条件字符串(如 `'a > 5, 0
在实际开发中,常需根据用户输入的灵活条件(如 'a > 5, 0 < (cd) < 6, ef < 35')动态校验字典内容是否全部满足。虽然可手动 split(',') 后逐条提取键、运算符和值再比对,但该方式易出错、难以支持链式比较(如 0 < x < 6),且直接使用 eval() 存在严重安全风险(任意代码执行)。本文提供一个安全、健壮、可扩展的纯 Python 解决方案。
核心设计思路
本方案不依赖外部库(如 Pandas),而是通过以下三步实现优雅解析:
- 运算符映射:使用 operator 模块将字符串运算符('>', '!=' 等)映射为可调用函数;
- 结构化解析:利用正则表达式精准切分运算符与操作数,区分字典键名(如 'a'、'(cd)')与字面量(数字、字符串、布尔值、None);
- 类型自动推导:cast() 函数智能识别并转换数值(int/float)、字符串(带引号)、布尔值及 None,确保类型安全比较。
完整实现代码
import operator
import re
# 运算符到函数的映射(支持常见比较操作)
operators = {
'<=': operator.le,
'>=': operator.ge,
'>': operator.gt,
'<': operator.lt,
'==': operator.eq,
'!=': operator.ne
}
# 构建正则:匹配任意已注册运算符(转义特殊字符)
_oper = '|'.join(map(re.escape, operators.keys()))
oper_pattern = re.compile(fr'\s*({_oper})\s*').split
number_pattern = re.compile(r'-?\d*(\.\d+)?').fullmatch
string_pattern = re.compile(r'("|\')(?P<str>.*)\1').fullmatch
def cast(value: str) -> float | int | bool | str | None:
"""将字符串字面量安全转换为对应 Python 类型"""
v = value.strip()
lower_v = v.lower()
if lower_v in ('true', 'false'):
return lower_v == 'true'
elif m := string_pattern(v):
return m.group('str')
elif m := number_pattern(v):
return float(v) if '.' in v else int(v)
elif v == 'None':
return None
else:
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: 条件语法错误或无法解析字面量时抛出
"""
all_results = []
for cond in conditions.split(','):
cond = cond.strip()
if not cond:
continue
values, ops = [], []
# 按运算符切分,交替获取操作数和运算符
tokens = oper_pattern(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 and token not in data:
val = cast(token)
values.append(val)
# 验证结构:n 个运算符必须对应 n+1 个操作数
if len(values) != len(ops) + 1:
raise ValueError(f"条件格式错误: '{cond}' — 运算符与操作数数量不匹配")
# 执行链式比较:[v0, v1, v2] + [op0, op1] → op0(v0,v1) and op1(v1,v2)
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"类型不兼容比较 '{values[i]} {list(operators.keys())[list(operators.values()).index(ops[i])]} {values[i+1]}': {e}")
all_results.append(chain_ok)
return all(all_results)使用示例
# 示例数据
data = {
'a': 25,
'ab': 3.3,
'(cd)': 4,
'ef': 35,
'gh': 12.2,
'ij': "hello",
'kl': False,
'mn': None
}
# 多种条件组合(支持链式、混合类型、引号字符串)
conditions = "a > 5, 0 < (cd) < 6, ef < 35, ij == 'hello', mn == None"
if check_conditions(data, conditions):
print("✅ 所有条件均满足")
else:
print("❌ 至少一个条件未满足") # 此处输出 ❌,因 'ef < 35' 为 False关键优势与注意事项
- ✅ 安全无 eval:完全规避代码注入风险,仅解析受信运算符与字面量;
- ✅ 支持链式比较:0 < (cd) < 6 被正确拆解为 0 < (cd) 和 (cd) < 6 两次独立判断;
- ✅ 类型智能推导:自动识别 12.5(float)、42(int)、"text"(str)、True/False、None;
- ⚠️ 键名限制:字典键需为合法标识符或含括号/符号的字符串(如 '(cd)'),但不可含空格或运算符(如 'a b' 会解析失败);
- ⚠️ 无数学表达式支持:不支持 a + b > 10 等算术运算,仅支持单键 + 运算符 + 字面量的原子条件;
- ? 扩展建议:如需支持更复杂语法(如逻辑运算符 and/or、函数调用),推荐集成 pyparsing 或 lark 等专业解析器。
该方案在简洁性、安全性与实用性之间取得良好平衡,适用于配置驱动型校验、规则引擎轻量级实现等场景。










