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