pyparsing nestedExpr和嵌套括号

2024-06-29 00:32:35 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在研究一个非常简单的“查询语法”,它可供具有合理技术技能的人使用(即,不是程序员本身,但能够触及主题)

他们在表格上输入的内容的典型示例是:

address like street
AND
vote =  True
AND
(
  (
    age>=25
    AND
    gender = M
  )
  OR
  (
    age between [20,30]
    AND
    gender = F
  )
  OR
  (
    age >= 70
    AND
    eyes != blue
  )
)

  1. 无需报价
  2. 括号的潜在无限嵌套
  3. 简单和|或连接

我正在使用pyparsing(好吧,无论如何都在尝试)并达到以下目的:

^{pr2}$

输出为:

[['address',
  'like',
  'street',
  'AND',
  'vote',
  '=',
  'True',
  'AND',
  [['age>=25', 'AND', 'gender', '=', 'M'],
   'OR',
   ['age', 'between', '[20,30]', 'AND', 'gender', '=', 'F'],
   'OR',
   ['age', '>=', '70', 'AND', 'eyes', '!=', 'blue']]]]

我只对其部分满意-主要原因是期望的最终输出如下所示:

[
  {
    "field" : "address",
    "operator" : "like",
    "value" : "street",
  },
  'AND',
  {
    "field" : "vote",
    "operator" : "=",
    "value" : True,
  },
  'AND',
  [
    [
      {
        "field" : "age",
        "operator" : ">=",
        "value" : 25,
      },
      'AND'
      {
        "field" : "gender",
        "operator" : "=",
        "value" : "M",
      }
    ],
    'OR',
    [
      {
        "field" : "age",
        "operator" : "between",
        "value" : [20,30],
      },
      'AND'
      {
        "field" : "gender",
        "operator" : "=",
        "value" : "F",
      }
    ],
    'OR',
    [
      {
        "field" : "age",
        "operator" : ">=",
        "value" : 70,
      },
      'AND'
      {
        "field" : "eyes",
        "operator" : "!=",
        "value" : "blue",
      }
    ],
  ]
]

非常感谢!在

编辑

在Paul的回答之后,代码就是这样的。显然,它的效果更好:-)

unicode_printables = u''.join(unichr(c) for c in xrange(65536)
                              if not unichr(c).isspace())

user_input = ' '.join(user_input.split())

AND = oneOf(['AND', '&'])
OR = oneOf(['OR', '|'])
FIELD = Word(alphanums)
OPERATOR = oneOf(OPERATORS)
VALUE = Word(unicode_printables)
COMPARISON = FIELD + OPERATOR + VALUE

QUERY = infixNotation(
    COMPARISON,
    [
        (AND, 2, opAssoc.LEFT,),
        (OR, 2, opAssoc.LEFT,),
    ]
)

class ComparisonExpr:
    def __init__(self, tokens):
        self.tokens = tokens

    def __str__(self):
        return "Comparison:('field': {!r}, 'operator': {!r}, 'value': {!r})".format(*self.tokens.asList())

COMPARISON.addParseAction(ComparisonExpr)

RESULT = QUERY.parseString(user_input).asList()
print type(RESULT)
from pprint import pprint
pprint(RESULT)

输出为:

[
  [
    <[snip]ComparisonExpr instance at 0x043D0918>,
    'AND',
    <[snip]ComparisonExpr instance at 0x043D0F08>,
    'AND',
    [
      [
        <[snip]ComparisonExpr instance at 0x043D3878>,
        'AND',
        <[snip]ComparisonExpr instance at 0x043D3170>
      ],
      'OR',
      [
        [
          <[snip]ComparisonExpr instance at 0x043D3030>,
          'AND',
          <[snip]ComparisonExpr instance at 0x043D3620>
        ],
        'AND',
        [
          <[snip]ComparisonExpr instance at 0x043D3210>,
          'AND',
          <[snip]ComparisonExpr instance at 0x043D34E0>
        ]
      ]
    ]
  ]
]

有没有办法用字典而不是ComparisonExpr实例返回结果?在

编辑2

想出了一个天真而具体的解决方案,但到目前为止,它对我很有效:

[snip]
class ComparisonExpr:
    def __init__(self, tokens):
        self.tokens = tokens

    def __str__(self):
        return "Comparison:('field': {!r}, 'operator': {!r}, 'value': {!r})".format(*self.tokens.asList())

    def asDict(self):
        return {
            "field": self.tokens.asList()[0],
            "operator": self.tokens.asList()[1],
            "value": self.tokens.asList()[2]
        }

[snip]
RESULT = QUERY.parseString(user_input).asList()[0]
def convert(list):
    final = []
    for item in list:
        if item.__class__.__name__ == 'ComparisonExpr':
            final.append(item.asDict())
        elif item in ['AND', 'OR']:
            final.append(item)
        elif item.__class__.__name__ == 'list':
            final.append(convert(item))
        else:
            print 'ooops forgotten something maybe?'

    return final

FINAL = convert(RESULT)
pprint(FINAL)

哪些输出:

[{'field': 'address', 'operator': 'LIKE', 'value': 'street'},
   'AND',
   {'field': 'vote', 'operator': '=', 'value': 'true'},
   'AND',
   [[{'field': 'age', 'operator': '>=', 'value': '25'},
     'AND',
     {'field': 'gender', 'operator': '=', 'value': 'M'}],
    'OR',
    [[{'field': 'age', 'operator': 'BETWEEN', 'value': '[20,30]'},
      'AND',
      {'field': 'gender', 'operator': '=', 'value': 'F'}],
     'AND',
     [{'field': 'age', 'operator': '>=', 'value': '70'},
      'AND',
      {'field': 'eyes', 'operator': '!=', 'value': 'blue'}]]]]

再次感谢保罗给我指出了一个正确的方向!在

我唯一不知道的就是把'true'变成{},把{}变成{}。在


Tags: orandinstanceselffieldagevalueitem
1条回答
网友
1楼 · 发布于 2024-06-29 00:32:35

nestedExpr是pyparsing中的一个方便表达式,它使定义具有匹配的开头和结尾字符的文本变得容易。当您想解析嵌套的内容时,nestedExpr通常结构不好。在

使用pyparsing的infixNotation方法可以更好地处理您试图解析的查询语法。您可以在pyparsing wiki的示例页面上看到几个示例—SimpleBool与您正在解析的内容非常相似。在

“中缀表示法”是表达式的一般解析术语,其中运算符位于相关操作数之间(与“后缀表示法”相比,运算符跟在操作数之后,如“23+”而不是“2+3”;或“前缀表示法”,看起来像“+23”)。运算符在求值时可以有一个优先顺序,可以覆盖从左到右的顺序,例如,在“2+3*4”中,运算的优先级规定乘法在加法之前计算。中缀表示法还支持使用括号或其他分组字符来覆盖该优先级,如“(2+3)*4”中强制首先完成加法操作。在

pyparsing的infixNotation方法采用一个基本操作数表达式,然后是一个运算符定义元组的列表,按优先顺序排列。例如,4函数整数运算如下:

parser = infixNotation(integer,
             [
             (oneOf('* /'), 2, opAssoc.LEFT),
             (oneOf('+ -'), 2, opAssoc.LEFT),
             ])

这意味着我们将按顺序解析整数操作数,包括“*”和“/”二进制左关联运算以及“+”和“-”二进制运算。对括号重写顺序的支持内置于infixNotation。在

查询字符串通常是布尔操作NOT、AND、OR的某种组合,通常按优先顺序进行计算。在您的例子中,这些运算符的操作数是比较表达式,例如“address=street”或“age between[20,30]”。因此,如果您为比较表达式定义一个表达式,形式为fieldname operator value,那么您可以使用infixNotation对AND和OR进行正确的分组:

^{pr2}$

最后,我建议您定义一个类,将比较标记作为类init args,然后您可以将行为附加到该类以计算比较并输出调试字符串,如下所示:

class ComparisonExpr:
    def __init__(self, tokens):
        self.tokens = tokens

    def __str__(self):
        return "Comparison:('field': {!r}, 'operator': {!r}, 'value': {!r})".format(
                            *self.tokens.asList())

# attach the class to the comparison expression
comparison_expr.addParseAction(ComparisonExpr)

然后可以得到如下输出:

query_expr.parseString(sample).pprint()

[[Comparison:({'field': 'address', 'operator': 'like', 'value': 'street'}),
  'AND',
  Comparison:({'field': 'vote', 'operator': '=', 'value': True}),
  'AND',
  [[Comparison:({'field': 'age', 'operator': '>=', 'value': 25}),
    'AND',
    Comparison:({'field': 'gender', 'operator': '=', 'value': 'M'})],
   'OR',
   [Comparison:({'field': 'age', 'operator': 'between', 'value': [20, 30]}),
    'AND',
    Comparison:({'field': 'gender', 'operator': '=', 'value': 'F'})],
   'OR',
   [Comparison:({'field': 'age', 'operator': '>=', 'value': 70}),
    'AND',
    Comparison:({'field': 'eyes', 'operator': '!=', 'value': 'blue'})]]]]

在SimpleBool.py这个例子有更多的细节来展示如何创建这个类,以及NOT,and,and或操作符的相关类。在

编辑:

“有没有一种方法可以用字典而不是ComparisonExpr实例返回结果?” 正在调用您的ComparisonExpr类上的__repr__方法,而不是__str__。最简单的解决方案是在类中添加:

__repr__ = __str__

或者将__str__重命名为__repr__。在

“我唯一不知道的就是把‘真’变成真,‘20,30’‘变成了‘20,30’”

尝试:

CK = CaselessKeyword  # 'cause I'm lazy
bool_literal = (CK('true') | CK('false')).setParseAction(lambda t: t[0] == 'true')
LBRACK,RBRACK = map(Suppress, "[]")
# parse numbers using pyparsing_common.number, which includes the str->int conversion parse action
num_list = Group(LBRACK + delimitedList(pyparsing_common.number) + RBRACK)

然后将这些添加到值表达式中:

VALUE = bool_literal | num_list | Word(unicode_printables)

最后:

from pprint import pprint
pprint(RESULT)

我已经厌倦了导入pprint来完成这个任务,我只是把它添加到ParseResults的API中。尝试:

RESULT.pprint()  # no import required on your part

或者

print(RESULT.dump()) # will also show indented list of named fields

编辑2

最后,结果名称是很好的学习。如果将此更改为“比较”,则一切仍按现有方式工作:

COMPARISON = FIELD('field') + OPERATOR('operator') + VALUE('value')

但现在你可以写:

def asDict(self):
    return self.tokens.asDict()

您可以通过名称而不是索引位置访问解析后的值(使用result['field']表示法或result.field表示法)。在

相关问题 更多 >