Python eval和逻辑运算符

时间:2017-09-12 11:24:04

标签: python if-statement eval

我有一个JSON“数据库” - 它是一个JSON对象的python列表:

[{'_id': 'TRANSACTION0', 'Offer': {'From': 'merchant1', 'To': 'customer1', 'Item': 'Car', 'Price': 1000, 'Timestamp': 2}, 'Accept': {'Quantity': 1, 'Address': '123 Fake Street', 'Timestamp': 5}},
{'_id': 'TRANSACTION1', 'Offer': {'From': 'merchant1', 'To': 'customer2', 'Item': 'Computer', 'Price': 500, 'Timestamp': 5}},
{'_id': 'TRANSACTION2', 'Offer': {'From': 'merchant3', 'To': 'customer3', 'Item': 'Garbage bin', 'Price': 10, 'Timestamp': 0}, 'Accept': {'Quantity': 2, 'Address': '456 MadeUp Road', 'Timestamp': 1}},
{'_id': 'TRANSACTION3', 'Offer': {'From': 'merchant2', 'To': 'customer1', 'Item': 'Car', 'Price': 2000, 'Timestamp': 3}, 'Accept': {'Quantity': 2, 'Address': 'The White House', 'Timestamp': 3}},
{'_id': 'TRANSACTION4', 'Offer': {'From': 'merchant3', 'To': 'customer3', 'Item': 'Pens', 'Price': 2, 'Timestamp': 0}, 'Accept': {'Quantity': 4, 'Address': 'Houses of Parliment', 'Timestamp': 1}},
{'_id': 'TRANSACTION5', 'Offer': {'From': 'merchant4', 'To': 'customer1', 'Item': 'Headphones', 'Price': 200, 'Timestamp': 4}},
{'_id': 'TRANSACTION6', 'Offer': {'From': 'merchant1', 'To': 'customer2', 'Item': 'Water Bottle', 'Price': 1, 'Timestamp': 1}, 'Accept': {'Quantity': 3, 'Address': 'Timbuktu', 'Timestamp': 14}},
{'_id': 'TRANSACTION7', 'Offer': {'From': 'merchant2', 'To': 'customer3', 'Item': 'Laptop', 'Price': 900, 'Timestamp': 0}},
{'_id': 'TRANSACTION8', 'Offer': {'From': 'merchant4', 'To': 'customer1', 'Item': 'Chair', 'Price': 80, 'Timestamp': 3}, 'Accept': {'Quantity': 1, 'Address': 'Mordor', 'Timestamp': 3}},
{'_id': 'TRANSACTION9', 'Offer': {'From': 'merchant3', 'To': 'customer3', 'Item': 'Garbage bin', 'Price': 5, 'Timestamp': 2}, 'Accept': {'Quantity': 2, 'Address': 'The wall', 'Timestamp': 2}}]

我的目的是使用针对此数据库存储在词典中的查询。在此示例中,字典包含:

a_dict = {"query1": "'Offer' and 'Accept'"}

请注意,字典将包含更多查询,以及更复杂的查询(例如(cond1 and cond2) or (cond2 and cond3)),但我需要理解为什么Python正在做它正在做的事情(以及如何克服它),而不仅仅是解决方案是。

我需要有一些评估并正确运行query1的东西。我目前的错误实施是:

if (eval(a_dict["query1"]) + "in i"):

这与:

相同
if 'Offer' and 'Accept' in i:

由于短路,此评估仅检查Accept是否在i中。在此示例中,每次有Accept时都会有Offer,但情况可能并非总是如此。

合法的if语句将是:

if 'Offer' in i and 'Accept' in i:

但是,从我可能遇到的潜在查询类型来看,这并不容易组合。理想情况下,我想要一个优雅的解决方案,即“即插即用”,类似于上面给出的eval if语句。

无论如何能从字典中获取特定查询,将其插入if语句,然后按照我的意图运行if语句(假设所有查询具有逻辑意义)?

https://www.python.org/dev/peps/pep-0308/这篇文章说FAQ 4.16提供了替代方案,但我似乎无法在任何地方找到它

3 个答案:

答案 0 :(得分:3)

请不要使用eval进行查询。当你不期待它时,这可以保证在你的脸上爆炸。也许您已经听说过SQL注入;使用eval构建查询的安全隐患是巨大的。

基于过滤器的查询系统

相反,首先为常见查询编写过滤函数。这也将解决您的问题并提供即插即用的#34;撰写查询的方法。

这是一个关于如何实现它的指针:

将查询视为一个函数,它将一些文字值(以及隐式的一组记录)作为参数,并返回记录的结果集。抛弃列表并使用结果集的set数据类型(以记录ID为关键字)将大大提高性能。

然后" AND"变成一个函数,它接受两组(或多组)记录并构建它们的集合交集,并且" OR"成为一个函数,它接受两组(或多组)记录并构建它们的并集。 (不是整组记录与一个或多个子集之间的设定差异)。

如果以这种方式构建函数,查询将成为函数调用的简单树,例如:

result = q_and(q_or(q_merchant_is('merchant2'), 
                    q_address_is('123 FakeStreet')), 
               q_quantity_above(3))

(为了更好的易读性而格式化)

为构建此类查询的简单查询语言编写解析器并不难,但如果您不需要为最终用户提供前端,则可能不需要自己的查询语言,因为上面看到的查询的python表示简单而清晰。如果你确实需要将你的查询表示为字典,那么,如果你选择一个非常模仿查询调用树的最终结构的结构,那么编写一个query_builder函数会变得很简单。你的dict查询到一个函数,它将在调用时运行查询函数调用树。

注意:正如您所看到的,q_merchant_isq_quantity_above等不会过滤一组记录。您可以通过创建Query类并将完整集设置为实例属性来解决此问题,以便每个查询方法在需要时都可以访问完整记录集:

class Query(object):
    def __init__(self, all_records):
        self.records = all_records

    def merchant_is(self, name):
        result = set()
        for record in self.records:
            if record['Offer']['From'] == name:
               result.add(record['_id'])
        return result

    def q_and(self, *args):
        result = args[0]
        for i in range(1, len(args)):
            result = args[i].intersection(result)
        return result
    ...

q = Query(my_full_record_set)
result = q.q_and(q.q_or(q.merchant_is('merchant2').........))    

表现和指数

您会看到查询文字值的每个查询函数基本上扫描整个数据集以对其进行过滤。如果您的查询包含许多此类文字部分的搜索,则您将多次扫描数据集。对于大型数据集,这可能会变得过高。

一个简单的解决方案是在每个字段的一个字典中索引要查询的字段。这会使查询速度提高几个数量级,但如果您的数据发生变化,则需要确保将索引保持最新。

分类器查询系统

另一个解决方案是将查询函数构建为分类器而不是过滤器,这意味着merchant_is将采用文字值和记录并回答True或False,具体取决于记录是否包含该字面值。正确的领域。通过使用构建复合查询的工厂函数,我们可以使这一切有效地工作。

过滤器部分的示例查询将变为:

query = q_and(q_or(q_merchant_is('merchant2'),
                   q_address_is('123 FakeStreet')),
              q_quantity_above(3))
result = perform_query(query, all_my_records)

q_merchant_is会变成以下内容:

def q_merchant_is(literal):
    return lambda record: record['Orders']['From'] == literal

注意你如何返回一个函数,当用记录调用时, 将它归类。

q_or可能如下所示:

def q_or(*args):
    def or_template(record):
        for classifier in args:
            if classifier(record):
                return True
        return False
    return or_template

或有点terser(我不确定这是否更有效):

def q_or(*args):
    return lambda record: any([ classifier(record) for classifier in args])

q_or现在返回一个函数,该函数针对作为参数传递的记录运行多个分类器,如果至少有一个分类器返回True,则返回True。 q_and的工作方式与q_or类似,只是如果每个分类器返回True,它只返回True。如果q_not的分类器返回False,则def perform_query(query, all_records): return filter(query, all_records) 将返回True,反之亦然。

现在您只需要:

q_merchant_is

这只会迭代你的数据集一次,并且在没有涉及eval,compile和exec的情况下几乎可以在python中获得效率,但它比过滤器方法更难理解。

  

但是,根据我的潜在查询类型,这并不容易组合。理想情况下,我希望有一个优雅的解决方案,即即插即用"

使用过滤器和分类器系统,可以使用新的查询元素轻松扩展系统。在过滤器示例中,您将向Query类添加方法。在分类器示例中,您添加了一个查询函数构建器,就像我为{{1}}编写的那样。通常涉及两行python代码。

答案 1 :(得分:1)

没有任何功能或模块可以按您希望的方式自动解析您的查询。您必须编写自己的解析器并自己实现评估逻辑。

有些模块可以帮助您解析查询字符串;例如pyparsing。如果查询语法不是太复杂,您可以使用简单的字符串操作或者使用regex module来实现自己的解析器。

无论你最终使用什么: Do not use eval

答案 2 :(得分:1)

我真的怀疑你会在这里找到一个简单的“即插即用”解决方案。你能做的最好的事情就是为你的查询实现一个合适的minilanguage(解析器和解释器)。

好消息是可能 很难。如果你已经有编写解析器和解释器/编译器的工作经验,那么python不缺内置和第三部分工具,选择一个然后去。

另外还有an excellent python tutorial on Ruslan Pivack's blog named "let's build a simple interpreter"将指导您通过Python创建一个简单的Pascal解析器和解释器的整个过程,解释术语等。