【问题标题】:Keyword Matching in Pyparsing: non-greedy slurping of tokensPyparsing 中的关键字匹配:令牌的非贪婪 slurping
【发布时间】:2009-12-15 05:05:23
【问题描述】:

Python 爱好者:

假设你想使用 Pyparsing 解析以下字符串:

'ABC_123_SPEED_X 123'

ABC_123 是一个标识符; SPEED_X 是一个参数,123 是一个值。我想到了以下使用 Pyparsing 的 BNF:

Identifier = Word( alphanums + '_' )
Parameter = Keyword('SPEED_X') or Keyword('SPEED_Y') or Keyword('SPEED_Z')
Value = # assume I already have an expression valid for any value
Entry = Identifier + Literal('_') + Parameter + Value
tokens = Entry.parseString('ABC_123_SPEED_X 123')
#Error: pyparsing.ParseException: Expected "_" (at char 16), (line:1, col:17)

如果我从中间删除下划线(并相应地调整 Entry 定义)它会正确解析。

我怎样才能让这个解析器更懒一点,等到它与关键字匹配(而不是将整个字符串作为标识符并等待不存在的_

谢谢。

[注意:这是对我的问题的完全重写;我还没有意识到真正的问题是什么]

【问题讨论】:

  • 我知道很多:在分配Parameter 时,您应该使用|,而不是or
  • 这个问题的标题应该是“Non-greedy matching in pyparsing”。
  • @gotgenes:完成。做了一个更清晰的标题。
  • @Robert Rossney:事实上,我应该使用^,因为我想要尽可能长的匹配。谢谢!
  • @Arrieta: ^ 在这里是不必要的,因为所有 3 个选项的长度都相同。

标签: python parsing grammar pyparsing


【解决方案1】:

我的回答基于this one,因为您要做的是获得非贪婪匹配。看起来这在 pyparsing 中很难实现,但通过一些聪明和妥协​​并非不可能。以下似乎有效:

from pyparsing import *
Parameter = Literal('SPEED_X') | Literal('SPEED_Y') | Literal('SPEED_Z')
UndParam = Suppress('_') + Parameter
Identifier = SkipTo(UndParam)
Value = Word(nums)
Entry = Identifier + UndParam + Value

当我们从交互式解释器运行它时,我们可以看到以下内容:

>>> Entry.parseString('ABC_123_SPEED_X 123')
(['ABC_123', 'SPEED_X', '123'], {})

请注意,这是一种妥协;因为我使用SkipTo,所以Identifier 可以充满邪恶、恶心的字符,而不仅仅是漂亮的alphanums,偶尔会有下划线。

编辑:感谢 Paul McGuire,我们可以通过将 Identifier 设置为以下内容来构建一个真正优雅的解决方案:

Identifier = Combine(Word(alphanums) +
        ZeroOrMore('_' + ~Parameter + Word(alphanums)))

让我们看看它是如何工作的。首先忽略外层Combine;我们稍后再谈。从Word(alphanums) 开始,我们知道我们将获得引用字符串'ABC_123_SPEED_X 123''ABC' 部分。需要注意的是,在这种情况下,我们不允许“单词”包含下划线。我们将其单独构建到逻辑中。

接下来,我们需要捕获'_123' 部分,同时又不吸收'_SPEED_X'。让我们在这一点上也跳过ZeroOrMore,稍后再返回。我们将下划线作为Literal 开始,但我们可以只使用'_' 进行快捷方式,这将使我们成为前导下划线,但不是全部'_123'。本能地,我们会放置另一个Word(alphanums) 来捕获其余部分,但这正是消耗所有剩余的'_123_SPEED_X' 给我们带来麻烦的原因。相反,我们说,“只要下划线后面的内容不是Parameter,将其解析为我的Identifier的一部分。我们在pyparsing术语中将其声明为'_' + ~Parameter + Word(alphanums)。因为我们假设我们可以有任意数量的下划线 + WordButNotParameter 重复,我们将该表达式包装为 ZeroOrMore 构造。(如果您总是希望在首字母之后至少有下划线 + WordButNotParameter,您可以使用 OneOrMore。)

最后,我们需要将最初的 Word 和特殊的下划线 + Word 重复包装在一起,以便理解它们是连续的,而不是由空格分隔,因此我们将整个表达式包装在 Combine 构造中。这样'ABC _123_SPEED_X' 会引发解析错误,但'ABC_123_SPEED_X' 会正确解析。

还请注意,我必须将Keyword 更改为Literal,因为前者的方式过于微妙且容易激怒。我不信任Keywords,也无法与他们匹配。

【讨论】:

  • 这可能有效......我试图弄清楚为什么它适用于声明为文字集合的参数,并且如果参数声明为关键字集合则不起作用。谢谢!这可能就是答案!
  • 这与Keyword 的默认identChars 中的下划线有关。 crpppc19.epfl.ch/doc/python-pyparsing/htmldoc/… 如果您使用Keyword('SPEED_X', identChars=alphanums),您将获得匹配。但我会坚持使用Literal
  • 将标识符重新定义为Combine(Word(alphanums)+ZeroOrMore('_'+~Parameter+Word(alphanums))),我认为这将达到目标。 (顺便说一句,我很高兴看到更多 pyparsing 用户参与这些 SO 问题!)
  • 感谢您的解决方案,保罗。今天早上我想不出把零件放在一起的正确方法。您的解决方案非常优雅,因此我将其添加到答案中并进行了解释。
【解决方案2】:

如果您确定标识符永远不会以下划线结尾,则可以在定义中强制执行:

from pyparsing import *

my_string = 'ABC_123_SPEED_X 123'

Identifier = Combine(Word(alphanums) + Literal('_') + Word(alphanums))
Parameter = Literal('SPEED_X') | Literal('SPEED_Y') | Literal('SPEED_Z')
Value = Word(nums)
Entry = Identifier + Literal('_').suppress() + Parameter  + Value
tokens = Entry.parseString(my_string)

print tokens # prints: ['ABC_123', 'SPEED_X', '123']

如果不是这样,但如果标识符长度是固定的,您可以像这样定义标识符:

Identifier = Word( alphanums + '_' , exact=7)

【讨论】:

  • 不幸的是,如果标识符有两个以上的组件,则此替代方法将不起作用。例如。它因 my_string='ABC_123_Hola_SPEED_X 123' 而中断。
  • 非常接近,标识符只需要对多个组件多一点容忍度,加上负前瞻~Parameter 以避免意外读取参数作为标识符的一部分。
【解决方案3】:

您还可以将标识符和参数解析为一个标记,并在一个解析操作中将它们拆分:

from pyparsing import *
import re

def split_ident_and_param(tokens):
    mo = re.match(r"^(.*?_.*?)_(.*?_.*?)$", tokens[0])
    return [mo.group(1), mo.group(2)]

ident_and_param = Word(alphanums + "_").setParseAction(split_ident_and_param)
value = Word(nums)
entry = ident_and_param + value

print entry.parseString("APC_123_SPEED_X 123")

上面的例子假设标识符和参数的格式总是 XXX_YYY(包含一个下划线)。

如果不是这样,则需要调整split_ident_and_param()方法。

【讨论】:

  • +1 用于使用解析操作。不幸的是,OP 只发布了一种 Identifier,我们在稍后的帖子中了解到可能有 更多 两个下划线分隔的组件。我怀疑某些标识符也很可能只有一个组件,因此解析器确实必须注意这些下划线。这通常是解析问题的方式,示例数据通常是更大的可能和可能输入集的一个非常小的和特殊的子集。
【解决方案4】:

这回答了您可能也问过自己的一个问题:“reduce 的实际应用程序是什么?):

>>> keys = ['CAT', 'DOG', 'HORSE', 'DEER', 'RHINOCEROS']
>>> p = reduce(lambda x, y: x | y, [Keyword(x) for x in keys])
>>> p
{{{{"CAT" | "DOG"} | "HORSE"} | "DEER"} | "RHINOCEROS"}

编辑:

这是对原始问题的一个很好的答案。我将不得不着手开发新的。

进一步编辑:

我很确定你不能做你想做的事。 pyparsing 创建的解析器不进行前瞻。因此,如果您告诉它匹配 Word(alphanums + '_'),它将继续匹配字符,直到找到不是字母、数字或下划线的字符。

【讨论】:

  • 虽然速度很慢(我有一个很大的文件),但 Pyparsing 能够完成所需的任务。
  • Pyparsing 有 2 种前瞻方式,NotAny 类(使用 ~ 运算符的缩写)用于负前瞻,以及 FollowedBy 类(没有运算符快捷方式 - 我们必须在某处画线,或者我们还不如写 Perl) 来进行自信的前瞻。但是你是正确的,pyparsing not 在其语法中隐含地向前看“下一个匹配”,你必须使用这些结构之一自己编写代码。
  • 此外,可以通过以下几种方式之一来完成多个文字的链接:Or(map(Keyword, "RED GRN BLUE".split()))oneOf("RED GRN BLUE")。 oneOf 在这里实际上是首选,因为它具有内置的智能功能,能够区分单独的 ">" 和 ">=" 的主角。
  • real 晚点出现,但 FollowedBy 可以表示为 ~~(请参阅 Ford 关于 PEG 的原始论文:积极的前瞻逻辑上是消极前瞻的消极前瞻而 PEG 语法中的 & 运算符只是语法糖)。
猜你喜欢
  • 1970-01-01
  • 2017-10-16
  • 2015-02-11
  • 1970-01-01
  • 1970-01-01
  • 2011-12-10
  • 2013-06-09
  • 2012-11-17
  • 1970-01-01
相关资源
最近更新 更多