首页 > 解决方案 > PyParsing - 语法元素围绕其他元素拆分

问题描述

我正在移动一个工具(不是我写的)来使用 PyParsing。我正在更新语法以使其更有意义,但也希望向后兼容。语法包括被另一个元素分割的元素,我需要“动词”(包装元素)和“值”(包装元素)。(不,这些不是球体,尽管它们看起来像它们 - 这很令人困惑,也是我改变它的部分原因)。

*thing*  # verb: contains      value: thing
*thing   # verb: starts_with   value: thing
thing*   # verb: ends_with     value: thing
!*thing* # verb: not_contains  value: thing

我很难理解如何解析*thing*“动词”围绕“值”的位置。这些元素也在一个分隔列表中,尽管这部分我很好。

我将解析的完整示例:

command *thing*, !*other_thing*, *third_thing

我试过的:

import pyparsing as pp

command = pp.Keyword("command").setResultsName("command")
value = pp.Word(pp.alphanums + "_").setResultsName("value", listAllMatches=True)

contains = ("*" + value + "*").setResultsName("verb", listAllMatches=True)
not_contains = ("!*" + value + "*").setResultsName("verb", listAllMatches=True)
starts_with = ("*" + value).setResultsName("verb", listAllMatches=True)

verbs_and_values = (
    contains
    | not_contains
    | starts_with
)

directive = pp.Group(command + pp.delimitedList(verbs_and_values, delim=","))

example = "command *thing*, !*other_thing*, *third_thing"

result = directive.parseString(example)
print result.dump()

这让我得到了所有的价值,但动词是整个事情(即['*', 'thing', '*'])。我尝试使用类似于以下的 parseAction 调整动词:

def process_verb(tokens):
    if tokens[0] == '*' and tokens[-1] == '*':
        return "contains"
    # handle other verbs...

效果很好,但它破坏了价值观......

标签: pythonpyparsing

解决方案


我看到您正在使用结果名称listAllMatches=True来捕获 delimitedList 中的多个解析值。这对于简单的数据结构是可以的,但是一旦您想为给定值存储多个值,那么您将需要开始使用 Group 或解析操作类。

作为一般做法,我避免在低级表达式中使用结果名称,而是在使用 '+' 和 '|' 组合高级表达式时添加它们 运营商。我也主要使用expr("name")表单而不是expr.setResultsName("name")表单来设置结果名称。

这是使用组的代码的修改版本:

command = pp.Keyword("command")
value = pp.Word(pp.alphanums + "_")

contains = pp.Group("*" + value("value") + "*")
not_contains = pp.Group("!*" + value("value") + "*")
starts_with = pp.Group("*" + value("value"))

我还为命令和动词列表添加了结果名称directive

directive = pp.Group(command("command")
                     + pp.Group(pp.delimitedList(verbs_and_values, 
                                        delim=","))("verbs"))

现在这些表达式被包装在组中,没有必要使用listAllMatches=True,因为现在每个值都保存在自己单独的组中。

解析结果现在如下所示:

[['command', ['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]]
[0]:
  ['command', ['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]
  - command: 'command'
  - verbs: [['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]
    [0]:
      ['*', 'thing', '*']
      - value: 'thing'
    [1]:
      ['!*', 'other_thing', '*']
      - value: 'other_thing'
    [2]:
      ['*', 'third_thing']
      - value: 'third_thing'
  

您在使用解析操作来添加有关动词类型的信息的正确轨道上,但不是返回该值,而是希望将动词的类型添加为另一个命名结果。

def add_type_parse_action(verb_type):
    def pa(s, l, t):
        t[0]["type"] = verb_type
    return pa

contains.addParseAction(add_type_parse_action("contains"))
not_contains.addParseAction(add_type_parse_action("not_contains"))
starts_with.addParseAction(add_type_parse_action("starts_with"))

添加解析操作后,您将获得以下结果:

[['command', ['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]]
[0]:
  ['command', ['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]
  - command: 'command'
  - verbs: [['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]
    [0]:
      ['*', 'thing', '*']
      - type: 'contains'
      - value: 'thing'
    [1]:
      ['!*', 'other_thing', '*']
      - type: 'not_contains'
      - value: 'other_thing'
    [2]:
      ['*', 'third_thing']
      - type: 'starts_with'
      - value: 'third_thing'
      

您还可以定义类来为您的结果提供结构。由于类被“调用”就好像它是一个解析操作,Python 将使用解析的标记构造一个类实例:

class VerbBase:
    def __init__(self, tokens):
        self.tokens = tokens[0]

    @property
    def value(self):
        return self.tokens.value
    
    def __repr__(self):
        return "{}(value={!r})".format(type(self).__name__, self.value)

class Contains(VerbBase): pass
class NotContains(VerbBase): pass
class StartsWith(VerbBase): pass

contains.addParseAction(Contains)
not_contains.addParseAction(NotContains)
starts_with.addParseAction(StartsWith)

result = directive.parseString(example)
print(result.dump())

现在结果在对象实例中,其类型表明使用了哪种动词:

[['command', [Contains(value='thing'), NotContains(value='other_thing'), StartsWith(value='third_thing')]]]
[0]:
  ['command', [Contains(value='thing'), NotContains(value='other_thing'), StartsWith(value='third_thing')]]
  - command: 'command'
  - verbs: [Contains(value='thing'), NotContains(value='other_thing'), StartsWith(value='third_thing')]

注意:在整个问题中,您将命令后面的项目称为“动词”,我在此答案中保留了该名称,以便更容易与您的初始尝试进行比较。但通常,“动词”会指代一些动作,比如指令中的“命令”,而后面的项目更像是“限定词”或“参数”。名称在编码时很重要,不仅在与他人交流时,甚至在形成您自己的代码正在做什么的心理概念时。对我来说,这里的“动词”,通常是句子中的动作,更像是命令,而下面的部分我称之为“限定词”、“参数”或“主语”。


推荐阅读