如何在 Python 2 中评估字符串内的不等式



我有一个文本文件(我无法更改的不同进程的输出),其中包含逻辑比较(只有这三个:>, <=, in)存储为字符串。假设这是我文件中的一行,应该对其进行评估:

myStr = "x>2 and y<=30 and z in ('def', 'abc')"

我的一些变量是分类的,我指定了它们,其余的都是数字的:

categoricalVars = ('z')

我的变量的值存储在字典中,让我们假设这些是它们的值。请注意,它们总是以字符串的形式出现,即使对于数值变量也是如此:

x, y, z = '5', '6', 'abc'

所以我的问题是我如何安全地评估(即不使用eval())参考最后一行myStr的真实性。

我所做的是:首先更改myStr以反映数据类型:

import re
delim = "(>|<=| in )" # Put in group to find later which delimiter is used
def pyRules(s):
varName = re.split(delim, s)[0]
rest = "".join(re.split(delim, s)[1:])
if varName in categoricalVars:
return varName + rest
else:
return "float(" + varName + ")" + rest
# Call:
[pyRules(e) for e in myStr.split(' and ')] 
# Result:
['float(x)>2', 'float(y)<=30', "z in ('def', 'abc')"]

现在我可以轻松做到:

[eval(pyRules(e)) for e in myStr.split(' and ')]
# Result:
[True, True, True]

但我想避免这种情况。我尝试ast.literal_eval()但出现以下错误:

import ast
[ast.literal_eval(pyRules(e)) for e in myStr.split(' and ')]
# Result:
Traceback (most recent call last):
File "<ipython-input-556-dae16951de03>", line 1, in <module>
ast.literal_eval(ast.parse(conds[0]))
File "C:ProgramDataAnaconda2libast.py", line 80, in literal_eval
return _convert(node_or_string)
File "C:ProgramDataAnaconda2libast.py", line 79, in _convert
raise ValueError('malformed string')
ValueError: malformed string

接下来,我尝试了以下方法,几乎给了我正确的答案:

def pyRules(s):
varName = re.split(delim, s)[0]
operation = "".join(re.split(delim, s)[1:])
if varName in categoricalVars:
return "'{" + varName + "}'" + operation
else:
return "float({" + varName + "})" + operation
rules = [pyRules(e).format(x='5',y='6',z='abc') for e in myStr.split(' and ')] 
# rules is:
['float(5)>2', 'float(6)<=30', "'abc' in ('def', 'abc')"]

我可以再次对此使用eval()并得到[True, True, True]但为了避免它,我定义了自己的不等式检查器函数:

def check(x):
first, operation, second = re.split(delim, x)
if operation == ">":
return first > second
elif operation == "<=":
return first <= second
elif operation == " in ":
return first in second
# Call:
[check(pyRules(e).format(x='5',y='6',z='abc')) for e in myStr.split(' and ')] 
# Result:
[True, False, True]

很难评估第二项,即:'float(6)<=30'我还根据这个 SO 线程使用operator模块重新创建了这个函数,这本质上是相同的,并得到了相同的结果。

我检查了pyparsing,无法让它工作(这甚至看起来很可怕,看看这个!)和SymPy,但不幸的是它也经常使用eval,如我提供的超链接中所述。

问题2:鉴于我100%确定我没有任何疯狂的字符串可以干扰os并擦除我的磁盘和其他类似疯狂的东西,是否可以使用eval

注意:这是我用Python 2构建的一个大代码的一部分,所以基于Python 2的答案将是理想的;但如果有人认为我的答案在这个领域,我可以转向Python 3。

经过几个小时的工作,我想出了让ast.literal_eval()工作的方法!我的逻辑是看,比如说,x>2的两面,即x2,通过使用literal_eval评估来确保它们是安全的,然后通过我的check()函数运行它,该函数进行评估。z in ('def', 'abc')也一样:首先确保z('def', 'abc')都是安全的,然后使用check()函数进行实际的布尔检查。

由于我完全信任我的输入,我本可以做更简单eval()的方法,但只是想加倍谨慎。并希望为有安全问题(用户输入等)并需要安全评估逻辑的每个人构建一些代码。希望它对某人有所帮助!

请在下面查看我的完整代码,欢迎任何意见/建议。

import re
import ast
myStr = "x>2 and y<=30 and z in ('def', 'abc')"
categoricalVars = ('z')
x, y, z = '5', '6', 'abc'
delim = "(>|<=| in )" # Put in group to find in the func check() which delimiter is used
def pyRules(s):
"""
Place {} around variable names so that we can str.format() in the func check()
"""
varName = re.split(delim, s)[0]
rest = "".join(re.split(delim, s)[1:])
return "'{" + varName + "}'" + rest
def check(x):
"""
If operation is > or <= then it is a numeric var, use double literal_eval to
parse floats e.g. "'5'" (dual quotes) to 5.0. This is equivalent to:
float(ast.literal_eval(first)). Else it is categorical, just literal_eval once
"""
first, operation, second = re.split(delim, x)
if operation == ">":
return ast.literal_eval(ast.literal_eval(first)) > ast.literal_eval(second)
elif operation == "<=":
return ast.literal_eval(ast.literal_eval(first)) <= ast.literal_eval(second)
elif operation == " in ":
return ast.literal_eval(first) in ast.literal_eval(second)
# These are my raw rules:
print [pyRules(e) for e in myStr.split(' and ')] 
# These are my processed rules:
print [pyRules(e).format(x='5',y='6',z='abc') for e in myStr.split(' and ')] 
# And these are my final results of logical evaluation:
print [check(pyRules(e).format(x='5',y='6',z='abc')) for e in myStr.split(' and ')] 

三条结果线的结果:

["'{x}'>2", "'{y}'<=30", "'{z}' in ('def', 'abc')"]
["'5'>2", "'6'<=30", "'abc' in ('def', 'abc')"]
[True, True, True]

谢谢!

最新更新