Повторный модуль Python - сохранение состояния? - PullRequest
12 голосов
/ 15 января 2009

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

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

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

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

Ответы [ 6 ]

5 голосов
/ 15 января 2009

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

4 голосов
/ 15 января 2009

Испытываете некоторые идеи ...

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

Это позволяет вам сделать 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 (это Лисп?).

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

1 голос
/ 16 января 2009

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

Несмотря на то, что он не является поточно-ориентированным, для многих простых сценариев это обеспечивает полезное сокращение.

1 голос
/ 15 января 2009
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) и получите его последнее значение. Легко злоупотреблять, но также легко настраивать и, если не слишком далеко зайти, получить удивительно чистый код.

1 голос
/ 15 января 2009

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

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()
0 голосов
/ 10 января 2012

Вероятно, самое простое решение - вернуться раньше, чтобы вы могли вернуться к созданию переменных, вместо того, чтобы выполнять немедленный тест.

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.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...