Python PyParsing-语法元素围绕其他元素拆分

Python PyParsing-语法元素围绕其他元素拆分,python,pyparsing,Python,Pyparsing,我正在移动一个工具(不是我写的)来使用PyParsing。我正在更新语法以使其更有意义,但也希望向后兼容。语法包括被另一个元素分割的元素,我需要“动词”(包装元素)和“值”(包装元素)。(不,这些不是地球仪,尽管它们看起来很像——这让人困惑,这也是我改变它的部分原因) 我很难理解如何解析像*thing*这样的东西,其中“动词”围绕着“值”。这些元素也在一个分隔列表中,尽管我对这一部分没意见 我将要分析的内容的完整示例: command *thing*, !*other_thing*, *thir

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

我很难理解如何解析像
*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'、'*']
)。我尝试用类似以下的动作调整动词:

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

这很好,但它会将值吹走…

我发现您正在使用结果名称和
listalmatches=True
来捕获分隔列表中的多个解析值。这对于简单的数据结构来说是可以的,但是一旦您想要为给定的值存储多个值,那么您就需要开始使用Group或parse action类

一般来说,我避免在低级表达式上使用结果名称,而是在使用“+”和“|”运算符编写高级表达式时添加它们。我也主要使用
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 = 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')]

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

谢谢你给出了一个非常棒的答案!我不知道对结果进行分组会保留它们,并使listAllMatches变得不必要,这现在更有意义了。Re:动词,我想这实际上是我在MWE中命名不好的“命令”。“Is”和“contains”等是词性意义上的动词,但您的轻推让我再次看到了生产代码中的命名,而且肯定有更好的选择-因为这是访问ORM,我将使用ORM中的术语(“字段”、“关系”、“值”,而不是“命令”、“动词”、“值”)。也没有意识到您可以在parseAction中修改结果并添加其他内容-这里有很多好东西,再次感谢!
[['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')]