Python re模块 - 保存状态?

时间:2009-01-15 15:09:57

标签: python regex

我在Python中发现的最大烦恼之一是re模块无法在匹配对象中明确地保存其状态而无法保存其状态。通常,需要解析行,如果它们符合某个正则表达式,则从相同的正则表达式中取出它们的值。我想写这样的代码:

if re.match('foo (\w+) bar (\d+)', line):
  # do stuff with .group(1) and .group(2)
elif re.match('baz whoo_(\d+)', line):
  # do stuff with .group(1)
# etc.

但遗憾的是,无法找到前一次调用re.match的匹配对象,所以这样写的是:

m = re.match('foo (\w+) bar (\d+)', line)
if m:
  # do stuff with m.group(1) and m.group(2)
else:
  m = re.match('baz whoo_(\d+)', line)
  if m:
    # do stuff with m.group(1)

随着elif列表的增长,这种方法变得不那么方便而且变得非常笨拙。

一个hackish解决方案是将re.match和re.search包装在我自己的对象中,以保持状态。有没有人用过这个?您是否了解半标准实现(在大型框架或其他内容中)?

您可以推荐哪些其他解决方法? 或者,我是否只是滥用模块并以更清洁的方式实现我的需求?

提前致谢

7 个答案:

答案 0 :(得分:5)

您可能希望this module实现您正在寻找的包装器。

答案 1 :(得分:4)

尝试一些想法......

看起来您理想地想要一个带副作用的表达式。如果在Python中允许这样做:

if m = re.match('foo (\w+) bar (\d+)', line):
  # do stuff with m.group(1) and m.group(2)
elif m = re.match('baz whoo_(\d+)', line):
  # do stuff with m.group(1)
elif ...

...然后你会清楚而干净地表达你的意图。但事实并非如此。如果嵌套函数允许副作用,您可以:

m = None
def assign_m(x):
  m = x
  return x

if assign_m(re.match('foo (\w+) bar (\d+)', line)):
  # do stuff with m.group(1) and m.group(2)
elif assign_m(re.match('baz whoo_(\d+)', line)):
  # do stuff with m.group(1)
elif ...

现在,不仅变得丑陋,而且它仍然无效的Python代码 - 嵌套函数'assign_m'不允许修改外部作用域中的变量m。我能想到的最好的是真的丑陋,使用嵌套类可以产生副作用:

# per Brian's suggestion, a wrapper that is stateful
class m_(object):
  def match(self, *args):
    self.inner_ = re.match(*args)
    return self.inner_
  def group(self, *args):
    return self.inner_.group(*args)
m = m_()

# now 'm' is a stateful regex
if m.match('foo (\w+) bar (\d+)', line):
  # do stuff with m.group(1) and m.group(2)
elif m.match('baz whoo_(\d+)', line):
  # do stuff with m.group(1)
elif ...

明显过度杀伤。

您可以考虑使用内部函数来允许本地范围退出,这允许您删除else嵌套:

def find_the_right_match():
  # now 'm' is a stateful regex
  m = re.match('foo (\w+) bar (\d+)', line)
  if m:
    # do stuff with m.group(1) and m.group(2)
    return # <== exit nested function only
  m = re.match('baz whoo_(\d+)', line)
  if m:
    # do stuff with m.group(1)
    return

find_the_right_match()

这可以让你将嵌套=(2 * N-1)展平为嵌套= 1,但是你可能刚刚移动了副作用问题,并且嵌套函数很可能会混淆大多数Python程序员。

最后,有无副作用的方法来解决这个问题:

def cond_with(*phrases):
  """for each 2-tuple, invokes first item.  the first pair where
  the first item returns logical true, result is passed to second
  function in pair.  Like an if-elif-elif.. chain"""
  for (cond_lambda, then_lambda) in phrases:
    c = cond_lambda()
    if c:
      return then_lambda(c) 
  return None


cond_with( 
  ((lambda: re.match('foo (\w+) bar (\d+)', line)), 
      (lambda m: 
          ... # do stuff with m.group(1) and m.group(2)
          )),
  ((lambda: re.match('baz whoo_(\d+)', line)),
      (lambda m:
          ... # do stuff with m.group(1)
          )),
  ...)

现在代码几乎不像Python那样看起来像,更不用说Python程序员可以理解了(是Lisp吗?)。

我认为这个故事的寓意是Python并没有针对这种习惯进行优化。你真的需要有点冗长,并且有其他条件的大嵌套因子。

答案 2 :(得分:1)

您可以编写实用程序类来执行“保存状态和返回结果”操作。我不认为这就是那个hackish。实施起来相当简单:

class Var(object):
    def __init__(self, val=None): self.val = val

    def set(self, result):
        self.val = result
        return result

然后将其用作:

lastMatch = Var()

if lastMatch.set(re.match('foo (\w+) bar (\d+)', line)):
    print lastMatch.val.groups()

elif lastMatch.set(re.match('baz whoo_(\d+)', line)):
    print lastMatch.val.groups()

答案 3 :(得分:1)

class last(object):
  def __init__(self, wrapped, initial=None):
    self.last = initial
    self.func = wrapped

  def __call__(self, *args, **kwds):
    self.last = self.func(*args, **kwds)
    return self.last

def test():
  """
  >>> test()
  crude, but effective: (oYo)
  """
  import re
  m = last(re.compile("(oYo)").match)
  if m("abc"):
    print("oops")
  elif m("oYo"): #A
    print("crude, but effective: (%s)" % m.last.group(1)) #B
  else:
    print("mark")

if __name__ == "__main__":
  import doctest
  doctest.testmod()

last也适合作为装饰者。

意识到我努力使其自我测试并在2.5,2.6和3.0中工作,我在某种程度上模糊了真正的解决方案。重要的行标记为上面的#A和#B,您可以使用相同的对象进行测试(将其命名为matchis_somename)并检索其最后一个值。易于滥用,但也很容易调整,如果没有推得太远,可以获得令人惊讶的清晰代码。

答案 4 :(得分:1)

基于对这个问题的重大答案,我编造了以下机制。它似乎是解决Python中“无条件限制”限制的一般方法。重点是透明度,由无声授权实施:

class Var(object):
    def __init__(self, val=None):
        self._val = val

    def __getattr__(self, attr):
        return getattr(self._val, attr)

    def __call__(self, arg):
        self._val = arg
        return self._val


if __name__ == "__main__":
    import re

    var = Var()

    line = 'foo kwa bar 12'

    if var(re.match('foo (\w+) bar (\d+)', line)):
        print var.group(1), var.group(2)
    elif var(re.match('baz whoo_(\d+)', line)):
        print var.group(1)

在一般情况下,这是一个线程安全的解决方案,因为您可以创建自己的Var实例。为了在线程不成问题时更易于使用,可以导入和使用默认的Var对象。这是一个包含Var类的模块:

class Var(object):
    def __init__(self, val=None):
        self._val = val

    def __getattr__(self, attr):
        return getattr(self._val, attr)

    def __call__(self, arg):
        self._val = arg
        return self._val

var = Var()

这是用户的代码:

from var import Var, var
import re

line = 'foo kwa bar 12'

if var(re.match('foo (\w+) bar (\d+)', line)):
    print var.group(1), var.group(2)
elif var(re.match('baz whoo_(\d+)', line)):
    print var.group(1)

虽然不是线程安全的,但对于许多简单的脚本,这提供了一个有用的快捷方式。

答案 5 :(得分:1)

Python 3.8现在为我们提供了一个简洁的解决方案::=walrus operator)。

它将右侧的值分配给左侧的变量,然后返回该值。

基本上,我们终于可以实现@aaron的愿望,只需写:

if m := re.match('foo (\w+) bar (\d+)', line):
  # do stuff with m.group(1) and m.group(2)
elif m := re.match('baz whoo_(\d+)', line):
  # do stuff with m.group(1)
elif ...

答案 6 :(得分:0)

可能最简单的解决方案是尽早返回,这样您就可以回到创建变量而不需要立即进行测试。

def get_results(line):
    m = re.match('foo (\w+) bar (\d+)', line)
    if m:
      # do stuff with .group(1) and .group(2)
      return result
    m = re.match('baz whoo_(\d+)', line)
    if m:
      # do stuff with .group(1)
      return other_result
    # etc.

这样可以避免过度嵌套。