PyParsing 或语句

5 投票
2 回答
6970 浏览
提问于 2025-04-16 16:29

这件事其实很简单,但我想要匹配以下两种模式中的一种:

"GET /ligonier-broadcast-media/mp3/rym20110421.mp3 HTTP/1.1"

或者

-

我试过类似这样的代码:

key = Word(alphas + nums + "/" + "-" + "_" + "." + "?" + "=" + "%" + "&")

uri = Or("-" | Group(
                   Suppress("\"") +
                   http_method +
                   key.setResultsName("request_uri") +
                   http_protocol +
                   Suppress("\"")
               )
      )

但似乎没有匹配上。我对如何使用 Or() 不是很确定,也不清楚是否需要用 Group(),或者该怎么做。我知道在 Group() 里面提供的参数如果单独调用是可以工作的,但我真的需要匹配到破折号或者带引号的 URI 字符串,而不是只要其中一个。

日志格式是固定的,我们只能处理已经给我们的内容。任何建议都会非常感谢。

2 个回答

1

我觉得你想要的是...

from pyparsing import oneOf
# more code here
uri = oneOf(["-", <insert long match expr here>])`
uri.matchString(someStringVar)
20

一般来说,在pyparsing中,Or、And、MatchFirst和Each这些类很少被直接使用。推荐的做法是用它们对应的运算符来代替。在你的例子中,你同时使用了这两种形式,这反而让事情变得复杂。

这是你表达式的简化版本:

key = Word(alphanums + "/-_.?=%&")
QUOT = Suppress('"')
uri = ("-" | QUOT
             + http_method
             + key("request_uri")
             + http_protocol
             + QUOT
      )

Word的参数是表示允许字符集合的字符串。如果只用一个参数(就像你这样),那么这个字符串就被理解为可以作为Word的一部分的字符集合。如果给出两个字符串,第一个表示允许的起始字符集合,第二个表示允许的主体字符集合(这在定义像变量名这样的东西时很有用,比如在Python中,变量名的起始字符只能是字母和'_',但主体部分可以包含数字。这可以写成Word(alphas+'_', alphanums+'_')。因为Word的参数只是字符串,所以不需要单独添加"/" + "-" + "_" + ...,只需将它们合并成一个字符串即可。

‘|’运算符用于分隔允许的选择,生成一个MatchFirst表达式。之所以叫MatchFirst,是因为解析器在找到第一个匹配的表达式后就会停止尝试。所以如果用Word(alphas) | Word(nums)来解析字符串“abc”,pyparsing根本不会尝试匹配Word(nums),因为第一个选择已经匹配了。如果你想匹配字母、字母和数字的组合,或者字母和数字的单词,并且想解析字符串“abc123”,那么这个解析器:

Word(alphas) | Word(nums) | Word(alphanums)

会用前面的Word(alphas)来解析字符串的开头“abc”。我们通常可以通过重新排列选择来解决这样的问题,比如:

Word(alphanums) | Word(alphas) | Word(nums)

但并不是所有情况都能这么简单处理。所以pyparsing也支持Or表达式,用‘^’运算符来定义(我选择这个符号是因为‘^’让我想起了测量长度的绘图工具)。Or表达式会尝试应用所有给定的选择,并选择最长的匹配。因此,你可以把我的小测试例子写成:

Word(alphas) ^ Word(nums) ^ Word(alphanums)

现在pyparsing在匹配“abc”时不会停止,而是会尝试所有的选择,最终选择第三个选择,匹配“abc123”,因为它提供了更长的匹配。

对于你的URI定义,没有必要使用Or匹配。解析器不会把前面的‘-’和引号中的HTTP命令字符串搞混。所以使用MatchFirst(你已经通过‘|’运算符实现了)是完全足够的。

还有一些其他事项:

  • 如果可以的话,不要在Python中写"\""。Python支持这两种引号字符就是为了这个原因。用'"'代替。反斜杠是给C程序员和Windows文件名用的。

  • expr.setResultsName("name")在pyparsing 1.4.6后简化为expr("name")。这种简化的语法确实提高了你解析器定义的可读性。

  • 只有在你想保持结果的一些结构,或者有重复结构且其中有带结果名称的内部表达式时,才使用Group。对于你的解析器来说,这并不是必需的,只会在结果上增加一个额外的列表容器,需要额外的[0]索引来获取解析的数据。

(如果你确实决定要显式调用OrAnd等,确保传递一个表达式列表,而不是仅仅把它们作为参数列出给表达式构造器——请参见为什么在我的用例中,pyparsing的有序选择会失败?,了解这样的错误如何搞砸事情,这也是我鼓励使用算术运算符来组合解析器的原因。)

撰写回答