【问题标题】:implement query language on python在python上实现查询语言
【发布时间】:2018-01-28 21:28:47
【问题描述】:

我正在寻找一种方法将我工作场所的过滤功能公开给其他开发人员和可选的客户。

问题

我想基于用户定义的过滤器对我的数据(python dicts)实现一种简单的查询语言,这些过滤器向我的其他开发人员以及后来向我们的客户公开。

  • 语言应该足够简单,以供非开发人员使用
  • 足够安全,可以避免在我的服务器上执行远程代码
  • 足够表达,可以像下面的例子那样查询数据

在我的 dict / json 数据上公开 SQL 接口会很棒(我不喜欢设置服务器)

示例场景

db = [
  {'first': 'john', 'last': 'doe', 'likes': ['cookies', 'http']},
  {'first': 'jane', 'last': 'doe', 'likes': ['cookies', 'donuts']},
  {'first': 'danny', 'last': 'foo', 'likes': ['http', 'donuts']},
]

query = '(first == "john" or last == "doe") and likes contains "cookies"'
results = run_query(db, query)

这应该返回(在结果中):

[
  {'first': 'john', 'last': 'doe', 'likes': ['cookies', 'http']},
  {'first': 'jane', 'last': 'doe', 'likes': ['cookies', 'donuts']},
]

注意:我不介意更改运营商名称,例如or -> OR contains -> inside 或其他任何东西,只要它是人类可读的并且保持语言的相同表达性

我尝试过的解决方案

DSL

我查看了一些 DSL 库,例如 PLY,但在我看来它们太复杂了,并且需要一些魔法才能完成任务(不确定从哪里开始以及是否值得)

插件

没有找到任何插件系统为我的用户公开沙盒功能(即更安全的评估)

JSON 查询包

我查看了TinyDB 和其他通过 json 实现某种 SQL 的方法,但如果没有大量自定义,我找不到可以工作的东西。 我还查看了pandasql,它总体上看起来不错,但没有维护库:(

有一个 lucene 包解析器 - luqum 基于 PLY 但它与我的语法树不同(它们有更多方法)并且 lib 没有真正维护,(我确实考虑稍微操纵这个 lib 以获得我想要什么)

SQLite

使用 SQLiteDB 加载我的所有数据(无论是否在内存中),然后对其运行 SQL 查询。没有测试它,但这应该很简单,将我的整个数据加载到 SQL 中只是为了运行我不想做的数据。

我愿意接受建议,甚至对如何改进上述解决方案以使其发挥作用

【问题讨论】:

  • 可能使用pandas 已经可以为您提供很多功能,尤其是DataFrame.query(..) 部分。
  • 感谢@WillemVanOnsem,但我可以将查询字符串传递给它吗?如果没有,我应该在传递给df.query(..) 之前解析查询字符串你知道怎么做吗?
  • 是的,但它不适用于会员检查(contains 等)

标签: python json python-3.x abstract-syntax-tree


【解决方案1】:

在使用 PLY 进行基于文本的查询之前,我会从常规 Python 类构建核心语言,如下所示:

class Match:
    def __init__(self, **target):
        [[self.key, self.value]] = target.items()
    def __call__(self, obj):
        return self.key in obj and self.value == obj[self.key]

class Contains:        
    def __init__(self, **target):
        [[self.key, self.value]] = target.items()
    def __call__(self, obj):
        return self.key in obj and self.value in obj[self.key]        

class Or:
    def __init__(self, *predicates):
        self.predicates = predicates
    def __call__(self, record):
        return any(predicate(record) for predicate in self.predicates)

class And:
    def __init__(self, *predicates):
        self.predicates = predicates
    def __call__(self, record):
        return all(predicate(record) for predicate in self.predicates)

def run_query(db, query):
    return filter(query, db)

if __name__ == '__main__':

    db = [
      {'first': 'john', 'last': 'doe', 'likes': ['cookies', 'http']},
      {'first': 'jane', 'last': 'doe', 'likes': ['cookies', 'donuts']},
      {'first': 'danny', 'last': 'foo', 'likes': ['http', 'donuts']},
    ]
    query = And(Or(Match(first='john'), Match(last='doe')), Contains(likes='cookies'))
    for result in run_query(db, query):
        print(result)

这个输出:

{'first': 'john', 'last': 'doe', 'likes': ['cookies', 'http']}
{'first': 'jane', 'last': 'doe', 'likes': ['cookies', 'donuts']}

【讨论】:

  • 感谢@Raymond Hettinger 提供优雅和pythonic 的解决方案。实际上我最终使用了 PLY 效果很好(在某些情况下需要一些魔法)。简单示例:hand.name = 'full-house'
【解决方案2】:

SQL 广为人知,并且经常受到最终用户的要求。我知道有几个选项可以在 python 中实现这一点。它们依赖于外部库,但得到了很好的支持。

小数据解决方案

将字典输入到pd.DataFrame,例如见pd.DataFrame.from_dict。然后通过pandasql库查询。看来您已经尝试过了,但我提到它是因为(根据我的经验)它确实做到了。

大数据解决方案

以 HDF5 格式保存您的数据。 pandas 数据帧和 numpy 数组可以通过 h5py 库以 HDF5 格式轻松存储。然后使用HDFql库查询HDF5文件。

【讨论】:

  • 谢谢,但我不喜欢使用 pandasql,因为它没有维护。我可能会使用 PLY 实现语法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多