Подтвердить что ты не робот

Python re module - сохранение состояния?

Одним из самых больших неприятностей, которые я нахожу в Python, является неспособность модуля re сохранить его состояние, явно не делая его в объекте match. Часто нужно разбирать строки и, если они соответствуют определенному регулярному выражению, извлекают значения из них одним и тем же регулярным выражением. Я хотел бы написать код следующим образом:

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 увеличивается дольше.

Хакерским решением было бы обернуть re.match и re.search в мои собственные объекты, которые где-то сохраняют состояние. Кто-нибудь использовал это? Знаете ли вы о полустандартных реализациях (в больших рамках или что-то еще)?

Какие еще обходные пути вы можете порекомендовать? Или, может быть, я просто злоупотребляю модулем и могу достичь моих потребностей более чистым способом?

Заранее спасибо

4b9b3361

Ответ 1

Попробуйте некоторые идеи...

Похоже, вам идеально хотелось бы выражение с побочными эффектами. Если это разрешено в 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 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 ...

Но это явно overkill.

Вы можете использовать внутреннюю функцию, чтобы разрешить локальные расширения области, что позволяет удалить else nesting:

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()

Это позволяет сгладить nesting = (2 * N-1) до nesting = 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

Вам может понравиться этот модуль, который реализует оболочку, которую вы ищете.

Ответ 3

Вы можете написать класс утилиты, чтобы выполнить операцию "сохранить состояние и вернуть результат". Я не думаю, что это хаки. Это довольно тривиально:

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

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

Основываясь на замечательных ответах на этот вопрос, я придумал следующий механизм. Это похоже на общий способ решения ограничения "отсутствия присвоения в условиях" 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

Наверное, самым простым решением является возвращение на раннем этапе, поэтому вы можете вернуться к созданию переменных вместо необходимости немедленного тестирования.

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.

Таким образом вы избегаете чрезмерного гнездования.