【问题标题】:Alternative to the `match = re.match(); if match: ...` idiom?替代 `match = re.match();如果匹配:...`成语?
【发布时间】:2010-11-12 05:59:31
【问题描述】:

如果您想检查某项是否与正则表达式匹配,如果是,请打印第一组,您就可以了..

import re
match = re.match("(\d+)g", "123g")
if match is not None:
    print match.group(1)

这完全是迂腐的,但是中间的match 变量有点烦人..

像 Perl 这样的语言通过为匹配组创建新的$1..$9 变量来做到这一点,比如..

if($blah ~= /(\d+)g/){
    print $1
}

来自this reddit comment

with re_context.match('^blah', s) as match:
    if match:
        ...
    else:
        ...

..我认为这是一个有趣的想法,所以我写了一个简单的实现:

#!/usr/bin/env python2.6
import re

class SRE_Match_Wrapper:
    def __init__(self, match):
        self.match = match

    def __exit__(self, type, value, tb):
        pass

    def __enter__(self):
        return self.match

    def __getattr__(self, name):
        if name == "__exit__":
            return self.__exit__
        elif name == "__enter__":
            return self.__name__
        else:
            return getattr(self.match, name)

def rematch(pattern, inp):
    matcher = re.compile(pattern)
    x = SRE_Match_Wrapper(matcher.match(inp))
    return x
    return match

if __name__ == '__main__':
    # Example:
    with rematch("(\d+)g", "123g") as m:
        if m:
            print(m.group(1))

    with rematch("(\d+)g", "123") as m:
        if m:
            print(m.group(1))

(这个功能理论上可以修补到_sre.SRE_Match对象中)

如果没有匹配项,您可以跳过with 语句的代码块的执行,这将简化为..

with rematch("(\d+)g", "123") as m:
    print(m.group(1)) # only executed if the match occurred

..但根据我从PEP 343 推断出的内容,这似乎是不可能的

有什么想法吗?正如我所说,这真的是微不足道的烦恼,几乎到了代码高尔夫的地步......

【问题讨论】:

  • “几乎到了打代码的地步”不同意。这完全是代码高尔夫。看不出设置变量来表示状态变化有什么“烦人”。
  • 我确实说过我很迂腐..
  • 类似的讨论已经在 SO:stackoverflow.com/questions/447086/…

标签: python idioms


【解决方案1】:

我不认为在这种情况下使用with 是解决方案。您必须在 BLOCK 部分(由用户指定)中引发异常,并让 __exit__ 方法返回 True 以“吞下”异常。所以它永远不会好看。

我建议使用类似于 Perl 语法的语法。制作您自己的扩展 re 模块(我称之为 rex)并让它在其模块命名空间中设置变量:

if rex.match('(\d+)g', '123g'):
    print rex._1

正如您在下面的 cmets 中看到的,此方法既不是范围安全的,也不是线程安全的。只有当您完全确定您的应用程序将来不会成为多线程并且从您使用 this 的范围调用的任何函数都将使用相同的时,您才会使用它方法。

【讨论】:

  • 小心这个。如果条件中的代码重用“rex”对象,它将中断,因此在这种情况下,为了安全起见,您需要在使用它时进行复制——这主要是取消了好处。除非您跳过一些 TLS 箍,否则它也不是线程安全的; re 对象上的 match() (应该是)完全线程安全的。我认为这些问题远远超过了这样做的好处——为了节省一行代码,必须牢记很多。
  • 在某些情况下,我相信好处大于问题。对于简单的单线程程序,我相信这种方法是可以的。然而“match = re.match(...); if match: ...”是惯用的python。我自己会一直这样。仍然对@Blixt 的回答 +1,以获得对原始问题的优雅、类似 perl 的回答。
  • 给出的例子是 Perl 的魔法变量,它们有同样的限制。我看不到任何其他“好看”的方法来使代码比获取对象并检查它是否不是None更短。
  • Elegant 和 Perl-like 是完全相反的描述。
  • 好吧,老实说,我认为根本不应该研究这些解决方案。为了节省一行代码,应该只考虑一个简单的解决方案。我什至不认为让我的代码线程安全是值得的,因为如果你想要线程安全的代码,你应该使用对象,正如已经证明的那样,在一个语句中分配和检查对象的唯一方法是Glenn 使用生成器发布的解决方法,这根本不是很直观。
【解决方案2】:

我不认为这是微不足道的。如果我经常编写这样的代码,我不想在我的代码周围添加多余的条件。

这有点奇怪,但您可以使用迭代器来做到这一点:

import re

def rematch(pattern, inp):
    matcher = re.compile(pattern)
    matches = matcher.match(inp)
    if matches:
        yield matches

if __name__ == '__main__':
    for m in rematch("(\d+)g", "123g"):
        print(m.group(1))

奇怪的是,它使用迭代器来处理不迭代的东西——它更接近于条件,乍一看它可能会为每个匹配产生多个结果。

上下文管理器不能完全跳过它的托管函数似乎很奇怪。虽然这不是“with”的明确用例之一,但它似乎是一种自然的扩展。

【讨论】:

  • 是的,如果 __enter__ 代码在 try 部分内执行,它可以让 __exit__ 代码控制异常(因为 __enter__ 代码可能会抛出一个特殊的被with 语句吞下的异常类型,有效地阻止了其中的任何代码执行。)现在,我没有找到解决这个问题的方法。
  • 如果 Python 允许在表达式中赋值就好了,比如 C: "if x = y():", "if not (x = y():"; 它会处理这个直截了当。
  • +1:这是保存一行代码的唯一可行解决方案。虽然使用生成器不是很直观,但它可以完成这项工作并且是范围安全和线程安全的。
  • @Glenn:是的,这是我的第一直觉,然后我记得 Python 不会这样做=P
  • 我不明白if matches: 这个东西。我看到匹配对象可以是truenone。为什么 python 让它变得混乱?为什么不只返回真/假呢?如果您有多个二元选择,则返回 -1,0,1 等。
【解决方案3】:

如果你在一个地方做很多这样的事情,这里有一个替代答案:

import re
class Matcher(object):
    def __init__(self):
        self.matches = None
    def set(self, matches):
        self.matches = matches
    def __getattr__(self, name):
        return getattr(self.matches, name)

class re2(object):
    def __init__(self, expr):
        self.re = re.compile(expr)

    def match(self, matcher, s):
        matches = self.re.match(s)
        matcher.set(matches)
        return matches

pattern = re2("(\d+)g")
m = Matcher()
if pattern.match(m, "123g"):
    print(m.group(1))
if not pattern.match(m, "x123g"):
    print "no match"

您可以使用与 re 相同的线程安全性编译一次正则表达式,为整个函数创建一个可重用的 Matcher 对象,然后您可以非常简洁地使用它。这还有一个好处,您可以以明显的方式反转它 - 使用迭代器来实现这一点,您需要传递一个标志来告诉它反转其结果。

不过,如果您只对每个函数进行一次匹配,这并没有多大帮助;您不想将 Matcher 对象保留在比这更广泛的上下文中;它会导致与 Blixt 的解决方案相同的问题。

【讨论】:

    【解决方案4】:

    这看起来不是很漂亮,但是您可以像这样使用getattr(object, name[, default]) 内置函数:

    >>> getattr(re.match("(\d+)g", "123g"), 'group', lambda n:'')(1)
    '123'
    >>> getattr(re.match("(\d+)g", "X23g"), 'group', lambda n:'')(1)
    ''
    

    要模仿 if match print group 流程,您可以(ab)这样使用for 语句:

    >>> for group in filter(None, [getattr(re.match("(\d+)g", "123g"), 'group', None)]):
            print(group(1))
    123
    >>> for group in filter(None, [getattr(re.match("(\d+)g", "X23g"), 'group', None)]):
            print(group(1))
    >>> 
    

    当然你可以定义一个小函数来做这些脏活:

    >>> matchgroup = lambda p,s: filter(None, [getattr(re.match(p, s), 'group', None)])
    >>> for group in matchgroup("(\d+)g", "123g"):
            print(group(1))
    123
    >>> for group in matchgroup("(\d+)g", "X23g"):
            print(group(1))
    >>> 
    

    【讨论】:

      【解决方案5】:

      不是完美的解决方案,但确实允许您为同一个 str 链接多个匹配选项:

      class MatchWrapper(object):
        def __init__(self):
          self._matcher = None
      
        def wrap(self, matcher):
          self._matcher = matcher
      
        def __getattr__(self, attr):
          return getattr(self._matcher, attr)
      
      def match(pattern, s, matcher):
        m = re.match(pattern, s)
        if m:
          matcher.wrap(m)
          return True
        else:
          return False
      
      matcher = MatchWrapper()
      s = "123g";
      if _match("(\d+)g", line, matcher):
        print matcher.group(1)
      elif _match("(\w+)g", line, matcher):
        print matcher.group(1)
      else:
        print "no match"
      

      【讨论】:

        【解决方案6】:

        另一个不错的语法是这样的:

        header = re.compile('(.*?) = (.*?)$')
        footer = re.compile('(.*?): (.*?)$')
        
        if header.match(line) as m:
            key, value = m.group(1,2)
        elif footer.match(line) as m
            key, value = m.group(1,2)
        else:
            key, value = None, None
        

        【讨论】:

          【解决方案7】:

          根据 Glen Maynard 的解决方案,我有另一种方法:

          for match in [m for m in [re.match(pattern,key)] if m]:
              print "It matched: %s" % match
          

          类似于 Glen 的解决方案,这会迭代 0(如果不匹配)或 1(如果匹配)次。

          不需要子,但结果不太整洁。

          【讨论】:

            【解决方案8】:

            这是我的解决方案:

            import re
            
            s = 'hello world'
            
            match = []
            if match.append(re.match('w\w+', s)) or any(match):
                print('W:', match.pop().group(0))
            elif match.append(re.match('h\w+', s)) or any(match):
                print('H:', match.pop().group(0))
            else:
                print('No match found')
            

            您可以根据需要使用尽可能多的 elif 子句。

            更好:

            import re
            
            s = 'hello world'
            
            if vars().update(match=re.match('w\w+', s)) or match:
                print('W:', match.group(0))
            elif vars().update(match=re.match('h\w+', s)) or match:
                print('H:', match.group(0))
            else:
                print('No match found')
            

            appendupdate 都返回 None。因此,您必须在每种情况下使用 or 部分来实际检查表达式的结果。

            不幸的是,这只适用于代码位于顶层的情况,即不在函数中。

            【讨论】:

              【解决方案9】:

              这就是我的工作:

              def re_match_cond (match_ref, regex, text):
                  match = regex.match (text)
                  del match_ref[:]
                  match_ref.append (match)
                  return match
              
              if __name__ == '__main__':
                  match_ref = []
                  if re_match_cond (match_ref, regex_1, text):
                      match = match_ref[0]
                      ### ...
                  elif re_match_cond (match_ref, regex_2, text):
                      match = match_ref[0]
                      ### ...
                  elif re_match_cond (match_ref, regex_3, text):
                      match = match_ref[0]
                      ### ...
                  else:
                      ### no match
                      ### ...
              

              也就是说,我将一个列表传递给函数以模拟传递引用。

              【讨论】:

                【解决方案10】:

                Python 3.8 开始,并引入assignment expressions (PEP 572):= 运算符),我们现在可以在变量re.match(r'(\d+)g', '123g') 中捕获条件值match 以检查它是否不是None 和然后在条件体中重新使用它:

                >>> if match := re.match(r'(\d+)g', '123g'):
                ...   print(match.group(1))
                ... 
                123
                >>> if match := re.match(r'(\d+)g', 'dddf'):
                ...   print(match.group(1))
                ...
                >>>
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-02-25
                  • 2018-02-22
                  • 2019-04-04
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-03-26
                  相关资源
                  最近更新 更多