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






Вам может понравиться этот модуль, который реализует оболочку, которую вы ищете.
Спасибо за указатель! Мне нравится основная концепция связанного рецепта, но ее можно улучшить, если вы используете более новую версию Python. У меня строго 2,5+, так что сейчас пойду поиграю в хаки.
Вы можете написать служебный класс для выполнения операции «сохранить состояние и вернуть результат». Я не думаю, что это так хакерски. Реализовать довольно просто:
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()
Это интересная концепция. Хм, он может справиться со многими случаями, когда неспособность Python использовать присваивание в выражении причиняет боль.
Пробуем идеи ...
Похоже, вам в идеале нужно выражение с побочными эффектами. Если бы это было разрешено в 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 не оптимизирован для такого рода идиом. Вам действительно нужно просто быть немного многословным и жить с большим фактором вложенности других условий.
LOL и ++ о коде Lispy. Хорошо продумано :-) Но я был бы очень недоволен любым программистом, который пишет такой реальный код ;-)
@eliben - хех, спасибо. могло быть и хуже ... по крайней мере, я не пробовал использовать call / cc!
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) и извлекаете его последнее значение. Легко злоупотребить, но также легко настроить и, если не зайти слишком далеко, получить удивительно чистый код.
Основываясь на прекрасных ответах на этот вопрос, я придумал следующий механизм. Это похоже на общий способ решить ограничение 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)
Хотя это не является поточно-ориентированным, для многих простых сценариев это дает полезный ярлык.
Вероятно, самое простое решение - вернуться пораньше, чтобы можно было вернуться к созданию переменных, вместо того, чтобы проводить немедленную проверку.
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.
Таким образом вы избежите чрезмерной вложенности.
Python 3.8 предоставил нам изящное решение: := (моржовый оператор).
Он присвоит правое значение левой переменной, а затем вернет значение.
По сути, мы можем наконец исполнить желание @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 ...
Хотелось бы, чтобы у модуля тоже была форма сохранения состояния. Но я не считаю хакерским создавать собственный объект, сохраняющий состояние. Мне кажется чистым, локализованным и четко определенным решением.