Python中的匹配组

93

在Python中,有没有一种方法可以在不显式创建匹配对象的情况下访问匹配组(或者另一种美化下面示例的方法)?

以下是一个示例,以阐明我提出这个问题的动机:

以下是Perl代码

if    ($statement =~ /I love (\w+)/) {
  print "He loves $1\n";
}
elsif ($statement =~ /Ich liebe (\w+)/) {
  print "Er liebt $1\n";
}
elsif ($statement =~ /Je t\'aime (\w+)/) {
  print "Il aime $1\n";
}

翻译成Python

m = re.search("I love (\w+)", statement)
if m:
  print "He loves",m.group(1)
else:
  m = re.search("Ich liebe (\w+)", statement)
  if m:
    print "Er liebt",m.group(1)
  else:
    m = re.search("Je t'aime (\w+)", statement)
    if m:
      print "Il aime",m.group(1)

这看起来非常笨拙(if-else级联,匹配对象创建)。


1
重复:https://dev59.com/1XVD5IYBdhLWcg3wAGiD - S.Lott
3
注意:Python中的re.match()仅匹配目标字符串的开头。因此,re.match("I love (\w+)", "Oh! How I love thee")不会匹配。你需要使用re.search()或者明确在正则表达式前加上适当的通配符模式来匹配字符串,例如re.match(".* I love (\w+)", ...)。 - Jim Dennis
@Jim Dennis:感谢您指出这一点;我已经相应地修改了Python示例。 - Curd
@S.Lott:哎呀,你说得对。我没看到,尽管在发布之前我一直在寻找;不过这里有很有价值的新答案。 - Curd
5个回答

75

您可以创建一个小类,该类返回调用match的布尔结果,并保留匹配组以供后续检索:

import re

class REMatcher(object):
    def __init__(self, matchstring):
        self.matchstring = matchstring

    def match(self,regexp):
        self.rematch = re.match(regexp, self.matchstring)
        return bool(self.rematch)

    def group(self,i):
        return self.rematch.group(i)


for statement in ("I love Mary", 
                  "Ich liebe Margot", 
                  "Je t'aime Marie", 
                  "Te amo Maria"):

    m = REMatcher(statement)

    if m.match(r"I love (\w+)"): 
        print "He loves",m.group(1) 

    elif m.match(r"Ich liebe (\w+)"):
        print "Er liebt",m.group(1) 

    elif m.match(r"Je t'aime (\w+)"):
        print "Il aime",m.group(1) 

    else: 
        print "???"

Python 3中print作为一个函数的更新和Python 3.8的赋值表达式——现在不再需要REMatcher类:

import re

for statement in ("I love Mary",
                  "Ich liebe Margot",
                  "Je t'aime Marie",
                  "Te amo Maria"):

    if m := re.match(r"I love (\w+)", statement):
        print("He loves", m.group(1))

    elif m := re.match(r"Ich liebe (\w+)", statement):
        print("Er liebt", m.group(1))

    elif m := re.match(r"Je t'aime (\w+)", statement):
        print("Il aime", m.group(1))

    else:
        print()

1
它可能会冗长,但您将把REMatcher类放入一个漂亮的模块中,每当需要时导入该模块。您不会为一个在未来不会再次出现的问题提出这个问题,对吧? - tzot
4
@ΤΖΩΤΖΙΟΥ:我同意,但为什么模块re中还没有这样的类呢? - Curd
17
我不同意。仅仅因为“成千上万的其他人”没有考虑引入它而感到满意是很愚蠢的。如果我不问“为什么”,怎么能确定没有不开设这样一门课程的好理由?我自己看不出来,但也许别人可以解释(从而更好地了解Python的哲学)。下面是一个很好的例子,说明这样的问题是富有成效的:https://dev59.com/E3RA5IYBdhLWcg3www3D - Curd
1
“Why”问题通常是有成效的,但你的问题属于“为什么不按照我喜欢的方式”这个子类(强调“我喜欢的方式”),这个问题无法回答。你认为这样的函数/类会非常有用,然后问为什么其他人没有采取行动。要发生变化,有动力的人(在这里是你)必须向社区的其他成员(在这里是Python社区)证明这种变化的合理性。向社区询问为什么你想要的变化还没有被引入,这是相当自我中心和无成效的。 - tzot
考虑C语言中的 if ( a = funcall() ) :将赋值结果用作条件。这个特性经常被误用,很容易在C代码中成为一个“陷阱”(也许这就是为什么它已经从其他语言中删除了),但是如果使用适当的运算符(当然不是 = ),它可以解决你的问题并成为一个不错的附加功能。例如,想象一下有一个运算符 =? ,你可以写成 if m =? re.search(...) : m.group(1) 。使用 elif ,你最终可能会编写类似于你发布的PERL片段的代码。因此,也许问题更通用(与re模块实现无关)。 - LRMAAX
显示剩余5条评论

33

虽然效率较低,但外观更简单:

m0 = re.match("I love (\w+)", statement)
m1 = re.match("Ich liebe (\w+)", statement)
m2 = re.match("Je t'aime (\w+)", statement)
if m0:
  print("He loves", m0.group(1))
elif m1:
  print("Er liebt", m1.group(1))
elif m2:
  print("Il aime", m2.group(1))

Perl的问题在于隐式更新某些隐藏变量。这在Python中很难实现,因为你需要一个赋值语句才能真正更新任何变量。
版本重复性较少(效率更高)的代码如下:
pats = [
    ("I love (\w+)", "He Loves {0}" ),
    ("Ich liebe (\w+)", "Er Liebe {0}" ),
    ("Je t'aime (\w+)", "Il aime {0}")
 ]
for p1, p3 in pats:
    m = re.match(p1, statement)
    if m:
        print(p3.format(m.group(1)))
        break

一些 Perl 程序员喜欢的一个小变化:

pats = {
    "I love (\w+)" : "He Loves {0}",
    "Ich liebe (\w+)" : "Er Liebe {0}",
    "Je t'aime (\w+)" : "Il aime {0}",
}
for p1 in pats:
    m = re.match(p1, statement)
    if m:
        print(pats[p1].format(m.group(1)))
        break

这几乎不值得一提,但有时Perl程序员会遇到这个问题。

4
@ S.Lott:好的,您的解决方案避免了 if-else 级联,但是以执行不必要的匹配为代价(如果 m0 匹配,m1 和 m2 就不需要了);这就是我对此解决方案不太满意的原因。 - Curd
如果您上一个版本中的键顺序很重要,请确保告诉 OP 使用 OrderedDict。 - PaulMcG

22

Python 3.8 开始,引入了 赋值表达式 (PEP 572) (:= 操作符),我们可以将条件值 re.search(pattern, statement) 存储在一个变量中(让我们称其为 match),以便在条件体内既检查它是否不是 None,又可以重复使用它:

if match := re.search('I love (\w+)', statement):
  print(f'He loves {match.group(1)}')
elif match := re.search("Ich liebe (\w+)", statement):
  print(f'Er liebt {match.group(1)}')
elif match := re.search("Je t'aime (\w+)", statement):
  print(f'Il aime {match.group(1)}')

2

这不是一个正则表达式的解决方案。

alist={"I love ":""He loves"","Je t'aime ":"Il aime","Ich liebe ":"Er liebt"}
for k in alist.keys():
    if k in statement:
       print alist[k],statement.split(k)[1:]

1
你可以创建一个辅助函数:
def re_match_group(pattern, str, out_groups):
    del out_groups[:]
    result = re.match(pattern, str)
    if result:
        out_groups[:len(result.groups())] = result.groups()
    return result

然后像这样使用它:

groups = []
if re_match_group("I love (\w+)", statement, groups):
    print "He loves", groups[0]
elif re_match_group("Ich liebe (\w+)", statement, groups):
    print "Er liebt", groups[0]
elif re_match_group("Je t'aime (\w+)", statement, groups):
    print "Il aime", groups[0]

它有点笨重,但它完成了工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接