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

时间:2018-01-08 02:30:38

标签: python python-2.7 parsing eval

我有一个文本文件(一个我无法改变的不同过程的输出),它包含存储为字符串的逻辑比较(只有这三个:>, <=, 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:\ProgramData\Anaconda2\lib\ast.py", line 80, in literal_eval
    return _convert(node_or_string)

  File "C:\ProgramData\Anaconda2\lib\ast.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'我还使用每个this SO threadoperator模块重新创建了这个函数,这基本上是相同的,并得到了同样的结果。

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

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

注意:这是我在Python 2中构建的一段大代码,因此基于Python 2的答案将是理想的;但如果有人认为我的答案是在那个领域,我可以转到Python 3。

1 个答案:

答案 0 :(得分:0)

经过几个小时的工作,我找到了让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]

谢谢!