代码之家  ›  专栏  ›  技术社区  ›  Eli Bendersky

Python的重新模块保存状态?

  •  12
  • Eli Bendersky  · 技术社区  · 17 年前

    我在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 s变长了。

    一个骇人的解决方案是将re.match和re.search封装在我自己的对象中,使状态保持在某个地方。有人用过这个吗?您是否知道半标准实现(在大型框架或其他方面)?

    您还可以推荐哪些其他解决方法? 或者,我只是误用了模块,可以以更干净的方式实现我的需求?

    6 回复  |  直到 17 年前
        1
  •  5
  •   Crescent Fresh    17 年前

    你可能会喜欢 this module 它实现了您正在寻找的包装器。

        2
  •  4
  •   Aaron    17 年前

    看起来您最希望看到一个具有副作用的表达式。如果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()
    

    最后,有一些无副作用的处理方法:

    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并没有针对这种习语进行优化。你真的需要有点冗长,在其他条件下有很大的嵌套因素。

        3
  •  1
  •   Brian    17 年前

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

    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()
    
        4
  •  1
  •   Roger Pate Roger Pate    17 年前
    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,在这里您使用相同的对象进行测试(命名它) match 或 is_somename

        5
  •  1
  •   Eli Bendersky    17 年前

    基于对这个问题的完美回答,我提出了以下机制。这似乎是解决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)
    

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

        6
  •  1
  •   Nick S    5 年前

    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 ...
    
        7
  •  0
  •   Tom Viner    14 年前

    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.
    

    这样可以避免过度嵌套。