代码之家  ›  专栏  ›  技术社区  ›  Dan Lenski

如何将这个正则表达式习语从Perl翻译成Python?

  •  48
  • Dan Lenski  · 技术社区  · 17 年前

    大约一年前,我从Perl转到Python,至今没有回头。只有 一个人 我发现在Perl中比在Python中更容易做到的习语:

    if ($var =~ /foo(.+)/) {
      # do something with $1
    } elsif ($var =~ /bar(.+)/) {
      # do something with $1
    } elsif ($var =~ /baz(.+)/) {
      # do something with $1
    }
    

    相应的Python代码并不那么优雅,因为if语句一直在嵌套:

    m = re.search(r'foo(.+)', var)
    if m:
      # do something with m.group(1)
    else:
      m = re.search(r'bar(.+)', var)
      if m:
        # do something with m.group(1)
      else:
        m = re.search(r'baz(.+)', var)
        if m:
          # do something with m.group(2)
    

    有人有一种优雅的方法在Python中重现这种模式吗?我见过匿名函数调度表的使用,但对于少数正则表达式来说,这些表似乎有点笨拙。..

    15 回复  |  直到 17 年前
        1
  •  6
  •   Community Mohan Dere    9 年前

    使用命名组和调度表:

    r = re.compile(r'(?P<cmd>foo|bar|baz)(?P<data>.+)')
    
    def do_foo(data):
        ...
    
    def do_bar(data):
        ...
    
    def do_baz(data):
        ...
    
    dispatch = {
        'foo': do_foo,
        'bar': do_bar,
        'baz': do_baz,
    }
    
    
    m = r.match(var)
    if m:
        dispatch[m.group('cmd')](m.group('data'))
    

    只需稍加自省,您就可以自动生成正则表达式和调度表。

        2
  •  18
  •   Thomas Wouters    17 年前
    r"""
    This is an extension of the re module. It stores the last successful
    match object and lets you access it's methods and attributes via
    this module.
    
    This module exports the following additional functions:
        expand  Return the string obtained by doing backslash substitution on a
                template string.
        group   Returns one or more subgroups of the match.
        groups  Return a tuple containing all the subgroups of the match.
        start   Return the indices of the start of the substring matched by
                group.
        end     Return the indices of the end of the substring matched by group.
        span    Returns a 2-tuple of (start(), end()) of the substring matched
                by group.
    
    This module defines the following additional public attributes:
        pos         The value of pos which was passed to the search() or match()
                    method.
        endpos      The value of endpos which was passed to the search() or
                    match() method.
        lastindex   The integer index of the last matched capturing group.
        lastgroup   The name of the last matched capturing group.
        re          The regular expression object which as passed to search() or
                    match().
        string      The string passed to match() or search().
    """
    
    import re as re_
    
    from re import *
    from functools import wraps
    
    __all__ = re_.__all__ + [ "expand", "group", "groups", "start", "end", "span",
            "last_match", "pos", "endpos", "lastindex", "lastgroup", "re", "string" ]
    
    last_match = pos = endpos = lastindex = lastgroup = re = string = None
    
    def _set_match(match=None):
        global last_match, pos, endpos, lastindex, lastgroup, re, string
        if match is not None:
            last_match = match
            pos = match.pos
            endpos = match.endpos
            lastindex = match.lastindex
            lastgroup = match.lastgroup
            re = match.re
            string = match.string
        return match
    
    @wraps(re_.match)
    def match(pattern, string, flags=0):
        return _set_match(re_.match(pattern, string, flags))
    
    
    @wraps(re_.search)
    def search(pattern, string, flags=0):
        return _set_match(re_.search(pattern, string, flags))
    
    @wraps(re_.findall)
    def findall(pattern, string, flags=0):
        matches = re_.findall(pattern, string, flags)
        if matches:
            _set_match(matches[-1])
        return matches
    
    @wraps(re_.finditer)
    def finditer(pattern, string, flags=0):
        for match in re_.finditer(pattern, string, flags):
            yield _set_match(match)
    
    def expand(template):
        if last_match is None:
            raise TypeError, "No successful match yet."
        return last_match.expand(template)
    
    def group(*indices):
        if last_match is None:
            raise TypeError, "No successful match yet."
        return last_match.group(*indices)
    
    def groups(default=None):
        if last_match is None:
            raise TypeError, "No successful match yet."
        return last_match.groups(default)
    
    def groupdict(default=None):
        if last_match is None:
            raise TypeError, "No successful match yet."
        return last_match.groupdict(default)
    
    def start(group=0):
        if last_match is None:
            raise TypeError, "No successful match yet."
        return last_match.start(group)
    
    def end(group=0):
        if last_match is None:
            raise TypeError, "No successful match yet."
        return last_match.end(group)
    
    def span(group=0):
        if last_match is None:
            raise TypeError, "No successful match yet."
        return last_match.span(group)
    
    del wraps  # Not needed past module compilation
    

    例如:

    if gre.match("foo(.+)", var):
      # do something with gre.group(1)
    elif gre.match("bar(.+)", var):
      # do something with gre.group(1)
    elif gre.match("baz(.+)", var):
      # do something with gre.group(1)
    
        3
  •  10
  •   Pat Notz    17 年前

    是啊,这有点烦人。也许这对你的案子有用。

    
    import re
    
    class ReCheck(object):
        def __init__(self):
            self.result = None
        def check(self, pattern, text):
            self.result = re.search(pattern, text)
            return self.result
    
    var = 'bar stuff'
    m = ReCheck()
    if m.check(r'foo(.+)',var):
        print m.result.group(1)
    elif m.check(r'bar(.+)',var):
        print m.result.group(1)
    elif m.check(r'baz(.+)',var):
        print m.result.group(1)
    

    编辑: 布莱恩正确地指出,我的第一次尝试没有成功。不幸的是,这次尝试的时间更长。

        4
  •  10
  •   Markus Jarderot    17 年前

    开始 Python 3.8 ,并介绍 assignment expressions (PEP 572) ( := 运算符),我们现在可以捕获条件值 re.search(pattern, text) 在变量中 match 以便两者都检查是否 None 然后在病症体内重复使用:

    if match := re.search(r'foo(.+)', text):
      # do something with match.group(1)
    elif match := re.search(r'bar(.+)', text):
      # do something with match.group(1)
    elif match := re.search(r'baz(.+)', text)
      # do something with match.group(1)
    
        5
  •  9
  •   Jack M.    17 年前

    我建议这样做,因为它使用最少的正则表达式来实现你的目标。它仍然是函数式代码,但并不比你的旧Perl差。

    import re
    var = "barbazfoo"
    
    m = re.search(r'(foo|bar|baz)(.+)', var)
    if m.group(1) == 'foo':
        print m.group(1)
        # do something with m.group(1)
    elif m.group(1) == "bar":
        print m.group(1)
        # do something with m.group(1)
    elif m.group(1) == "baz":
        print m.group(2)
        # do something with m.group(2)
    
        6
  •  4
  •   Thomas Wouters    17 年前

    感谢 this other SO question :

    import re
    
    class DataHolder:
        def __init__(self, value=None, attr_name='value'):
            self._attr_name = attr_name
            self.set(value)
        def __call__(self, value):
            return self.set(value)
        def set(self, value):
            setattr(self, self._attr_name, value)
            return value
        def get(self):
            return getattr(self, self._attr_name)
    
    string = u'test bar 123'
    save_match = DataHolder(attr_name='match')
    if save_match(re.search('foo (\d+)', string)):
        print "Foo"
        print save_match.match.group(1)
    elif save_match(re.search('bar (\d+)', string)):
        print "Bar"
        print save_match.match.group(1)
    elif save_match(re.search('baz (\d+)', string)):
        print "Baz"
        print save_match.match.group(1)
    
        7
  •  4
  •   Torsten Marek    17 年前

    或者,一些根本不使用正则表达式的东西:

    prefix, data = var[:3], var[3:]
    if prefix == 'foo':
        # do something with data
    elif prefix == 'bar':
        # do something with data
    elif prefix == 'baz':
        # do something with data
    else:
        # do something with var
    

    这是否合适取决于你的实际问题。别忘了,正则表达式不是Perl中的瑞士军刀;Python有不同的构造来进行字符串操作。

        8
  •  3
  •   Daniel Bingham    16 年前
    def find_first_match(string, *regexes):
        for regex, handler in regexes:
            m = re.search(regex, string):
            if m:
                handler(m)
                return
        else:
            raise ValueError
    
    find_first_match(
        foo, 
        (r'foo(.+)', handle_foo), 
        (r'bar(.+)', handle_bar), 
        (r'baz(.+)', handle_baz))
    

    为了加快速度,可以在内部将所有正则表达式转换为一个,并动态创建调度器。理想情况下,这将变成一门课。

        9
  •  1
  •   Matus    15 年前

    以下是我解决这个问题的方法:

    matched = False;
    
    m = re.match("regex1");
    if not matched and m:
        #do something
        matched = True;
    
    m = re.match("regex2");
    if not matched and m:
        #do something else
        matched = True;
    
    m = re.match("regex3");
    if not matched and m:
        #do yet something else
        matched = True;
    

    没有原始图案那么干净。然而,它简单明了,不需要额外的模块,也不需要更改原始的regexs。

        10
  •  1
  •   Mike Robins    9 年前

    用字典怎么样?

    match_objects = {}
    
    if match_objects.setdefault( 'mo_foo', re_foo.search( text ) ):
      # do something with match_objects[ 'mo_foo' ]
    
    elif match_objects.setdefault( 'mo_bar', re_bar.search( text ) ):
      # do something with match_objects[ 'mo_bar' ]
    
    elif match_objects.setdefault( 'mo_baz', re_baz.search( text ) ):
      # do something with match_objects[ 'mo_baz' ]
    
    ...
    

    但是,您必须确保没有重复的match_objects字典键(mo_foo、mo_bar等),最好为每个正则表达式指定自己的名称并相应地命名match_objects-key,否则match_objects.setdefault()方法将返回现有的match对象,而不是通过运行rexxx.search(text)创建新的match目标。

        11
  •  1
  •   Yirkha    8 年前

    极简主义的数据持有者:

    class Holder(object):
        def __call__(self, *x):
            if x:
                self.x = x[0]
            return self.x
    
    data = Holder()
    
    if data(re.search('foo (\d+)', string)):
        print data().group(1)
    

    或者作为单例函数:

    def data(*x):
        if x:
            data.x = x[0]
        return data.x
    
        12
  •  0
  •   Mike Robins    11 年前

    对Pat Notz的解决方案进行了一些扩展,我发现它甚至更优雅:
    -将方法命名为 re 提供(例如。 search() vs。 check() )以及
    -实施必要的方法,如 group() 在支架对象本身上:

    class Re(object):
        def __init__(self):
            self.result = None
    
        def search(self, pattern, text):
            self.result = re.search(pattern, text)
            return self.result
    
        def group(self, index):
            return self.result.group(index)
    

    例子

    而不是这样:

    m = re.search(r'set ([^ ]+) to ([^ ]+)', line)
    if m:
        vars[m.group(1)] = m.group(2)
    else:
        m = re.search(r'print ([^ ]+)', line)
        if m:
            print(vars[m.group(1)])
        else:
            m = re.search(r'add ([^ ]+) to ([^ ]+)', line)
            if m:
                vars[m.group(2)] += vars[m.group(1)]
    

    其中一个就是这样做的:

    m = Re()
    ...
    if m.search(r'set ([^ ]+) to ([^ ]+)', line):
        vars[m.group(1)] = m.group(2)
    elif m.search(r'print ([^ ]+)', line):
        print(vars[m.group(1)])
    elif m.search(r'add ([^ ]+) to ([^ ]+)', line):
        vars[m.group(2)] += vars[m.group(1)]
    

    最终看起来很自然,从Perl迁移时不需要太多的代码更改,并避免了像其他一些解决方案一样的全局状态问题。

        13
  •  0
  •   Jim Arlow    8 年前

    我的解决方案是:

    import re
    
    class Found(Exception): pass
    
    try:        
        for m in re.finditer('bar(.+)', var):
            # Do something
            raise Found
    
        for m in re.finditer('foo(.+)', var):
            # Do something else
            raise Found
    
    except Found: pass