Как вы переводите эту идиому регулярного выражения из Perl в Python?
Я перешел с Perl на Python около года назад и не оглянулся назад. Существует только one идиома, которую я когда-либо обнаружил, что я могу сделать более легко в Perl, чем в Python:
if ($var =~ /foo(.+)/) {
# do something with $1
} elsif ($var =~ /bar(.+)/) {
# do something with $1
} elsif ($var =~ /baz(.+)/) {
# do something with $1
}
Соответствующий код Python не настолько изящный, поскольку инструкции if сохраняют вложенность:
m = re.search(r'foo(.+)', var)
if m:
# do something with m.group(1)
else:
m = re.search(r'bar(.+)', var)
if m:
# do something with m.group(1)
else:
m = re.search(r'baz(.+)', var)
if m:
# do something with m.group(2)
Есть ли у кого-нибудь элегантный способ воспроизвести этот шаблон в Python? Я видел анонимные таблицы диспетчеризации функций, но они кажутся мне громоздкими для небольшого количества регулярных выражений...
Ответы
Ответ 1
Благодаря этому другому вопросу SO:
import re
class DataHolder:
def __init__(self, value=None, attr_name='value'):
self._attr_name = attr_name
self.set(value)
def __call__(self, value):
return self.set(value)
def set(self, value):
setattr(self, self._attr_name, value)
return value
def get(self):
return getattr(self, self._attr_name)
string = u'test bar 123'
save_match = DataHolder(attr_name='match')
if save_match(re.search('foo (\d+)', string)):
print "Foo"
print save_match.match.group(1)
elif save_match(re.search('bar (\d+)', string)):
print "Bar"
print save_match.match.group(1)
elif save_match(re.search('baz (\d+)', string)):
print "Baz"
print save_match.match.group(1)
Ответ 2
Использование названных групп и таблицы отправки:
r = re.compile(r'(?P<cmd>foo|bar|baz)(?P<data>.+)')
def do_foo(data):
...
def do_bar(data):
...
def do_baz(data):
...
dispatch = {
'foo': do_foo,
'bar': do_bar,
'baz': do_baz,
}
m = r.match(var)
if m:
dispatch[m.group('cmd')](m.group('data'))
С небольшим количеством интроспекции вы можете автоматически генерировать регулярное выражение и таблицу отправки.
Ответ 3
Да, это немного раздражает. Возможно, это сработает для вашего дела.
import re
class ReCheck(object):
def __init__(self):
self.result = None
def check(self, pattern, text):
self.result = re.search(pattern, text)
return self.result
var = 'bar stuff'
m = ReCheck()
if m.check(r'foo(.+)',var):
print m.result.group(1)
elif m.check(r'bar(.+)',var):
print m.result.group(1)
elif m.check(r'baz(.+)',var):
print m.result.group(1)
EDIT: Брайан правильно указал, что моя первая попытка не сработала. К сожалению, эта попытка длиннее.
Ответ 4
r"""
This is an extension of the re module. It stores the last successful
match object and lets you access it methods and attributes via
this module.
This module exports the following additional functions:
expand Return the string obtained by doing backslash substitution on a
template string.
group Returns one or more subgroups of the match.
groups Return a tuple containing all the subgroups of the match.
start Return the indices of the start of the substring matched by
group.
end Return the indices of the end of the substring matched by group.
span Returns a 2-tuple of (start(), end()) of the substring matched
by group.
This module defines the following additional public attributes:
pos The value of pos which was passed to the search() or match()
method.
endpos The value of endpos which was passed to the search() or
match() method.
lastindex The integer index of the last matched capturing group.
lastgroup The name of the last matched capturing group.
re The regular expression object which as passed to search() or
match().
string The string passed to match() or search().
"""
import re as re_
from re import *
from functools import wraps
__all__ = re_.__all__ + [ "expand", "group", "groups", "start", "end", "span",
"last_match", "pos", "endpos", "lastindex", "lastgroup", "re", "string" ]
last_match = pos = endpos = lastindex = lastgroup = re = string = None
def _set_match(match=None):
global last_match, pos, endpos, lastindex, lastgroup, re, string
if match is not None:
last_match = match
pos = match.pos
endpos = match.endpos
lastindex = match.lastindex
lastgroup = match.lastgroup
re = match.re
string = match.string
return match
@wraps(re_.match)
def match(pattern, string, flags=0):
return _set_match(re_.match(pattern, string, flags))
@wraps(re_.search)
def search(pattern, string, flags=0):
return _set_match(re_.search(pattern, string, flags))
@wraps(re_.findall)
def findall(pattern, string, flags=0):
matches = re_.findall(pattern, string, flags)
if matches:
_set_match(matches[-1])
return matches
@wraps(re_.finditer)
def finditer(pattern, string, flags=0):
for match in re_.finditer(pattern, string, flags):
yield _set_match(match)
def expand(template):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.expand(template)
def group(*indices):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.group(*indices)
def groups(default=None):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.groups(default)
def groupdict(default=None):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.groupdict(default)
def start(group=0):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.start(group)
def end(group=0):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.end(group)
def span(group=0):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.span(group)
del wraps # Not needed past module compilation
Например:
if gre.match("foo(.+)", var):
# do something with gre.group(1)
elif gre.match("bar(.+)", var):
# do something with gre.group(1)
elif gre.match("baz(.+)", var):
# do something with gre.group(1)
Ответ 5
Я бы предложил это, так как он использует наименьшее регулярное выражение для достижения вашей цели. Это по-прежнему функциональный код, но не хуже вашего старого Perl.
import re
var = "barbazfoo"
m = re.search(r'(foo|bar|baz)(.+)', var)
if m.group(1) == 'foo':
print m.group(1)
# do something with m.group(1)
elif m.group(1) == "bar":
print m.group(1)
# do something with m.group(1)
elif m.group(1) == "baz":
print m.group(2)
# do something with m.group(2)
Ответ 6
Альтернативно, что-то не использует регулярные выражения вообще:
prefix, data = var[:3], var[3:]
if prefix == 'foo':
# do something with data
elif prefix == 'bar':
# do something with data
elif prefix == 'baz':
# do something with data
else:
# do something with var
То, что подходит, зависит от вашей реальной проблемы. Не забывайте, что регулярные выражения - это не швейцарский армейский нож, который они находятся на Perl; Python имеет разные конструкции для выполнения строковых манипуляций.
Ответ 7
def find_first_match(string, *regexes):
for regex, handler in regexes:
m = re.search(regex, string):
if m:
handler(m)
return
else:
raise ValueError
find_first_match(
foo,
(r'foo(.+)', handle_foo),
(r'bar(.+)', handle_bar),
(r'baz(.+)', handle_baz))
Чтобы ускорить его, можно было превратить все регулярные выражения в один внутренний и создать диспетчера на лету. В идеале, тогда это будет превращено в класс.
Ответ 8
Вот как я решил эту проблему:
matched = False;
m = re.match("regex1");
if not matched and m:
#do something
matched = True;
m = re.match("regex2");
if not matched and m:
#do something else
matched = True;
m = re.match("regex3");
if not matched and m:
#do yet something else
matched = True;
Не так чисто, как исходный рисунок. Однако это просто, просто и не требует дополнительных модулей или что вы меняете исходные регулярные выражения.
Ответ 9
как насчет использования словаря?
match_objects = {}
if match_objects.setdefault( 'mo_foo', re_foo.search( text ) ):
# do something with match_objects[ 'mo_foo' ]
elif match_objects.setdefault( 'mo_bar', re_bar.search( text ) ):
# do something with match_objects[ 'mo_bar' ]
elif match_objects.setdefault( 'mo_baz', re_baz.search( text ) ):
# do something with match_objects[ 'mo_baz' ]
...
однако вы должны убедиться, что нет двухдисловых ключей словаря match_objects (mo_foo, mo_bar,...), лучше всего, указав каждому регулярному выражению свое собственное имя и соответствующим образом называя соответствующие ключи match_objects, иначе метод match_objects.setdefault() вернется существующий объект соответствия вместо создания нового объекта соответствия, запустив re_xxx.search(текст).
Ответ 10
Развернувшись на решении Pat Notz немного, я нашел его еще более элегантным:
- Назовите методы такими же, как re
(например, search()
vs. check()
) и
- выполнить необходимые методы, например group()
, на самом объекте-держателе:
class Re(object):
def __init__(self):
self.result = None
def search(self, pattern, text):
self.result = re.search(pattern, text)
return self.result
def group(self, index):
return self.result.group(index)
Пример
Вместо, например, это:
m = re.search(r'set ([^ ]+) to ([^ ]+)', line)
if m:
vars[m.group(1)] = m.group(2)
else:
m = re.search(r'print ([^ ]+)', line)
if m:
print(vars[m.group(1)])
else:
m = re.search(r'add ([^ ]+) to ([^ ]+)', line)
if m:
vars[m.group(2)] += vars[m.group(1)]
Делается только это:
m = Re()
...
if m.search(r'set ([^ ]+) to ([^ ]+)', line):
vars[m.group(1)] = m.group(2)
elif m.search(r'print ([^ ]+)', line):
print(vars[m.group(1)])
elif m.search(r'add ([^ ]+) to ([^ ]+)', line):
vars[m.group(2)] += vars[m.group(1)]
Выглядит очень естественно в конце, не нуждается в слишком большом количестве изменений кода при переходе с Perl и избегает проблем с глобальным состоянием, как некоторые другие решения.
Ответ 11
Минималистский DataHolder:
class Holder(object):
def __call__(self, *x):
if x:
self.x = x[0]
return self.x
data = Holder()
if data(re.search('foo (\d+)', string)):
print data().group(1)
или как одноэлементная функция:
def data(*x):
if x:
data.x = x[0]
return data.x
Ответ 12
Мое решение:
import re
class Found(Exception): pass
try:
for m in re.finditer('bar(.+)', var):
# Do something
raise Found
for m in re.finditer('foo(.+)', var):
# Do something else
raise Found
except Found: pass
Ответ 13
Вот класс RegexDispatcher, который отправляет его методы подкласса с помощью регулярного выражения.
Каждый диспетчерский метод аннотируется с регулярным выражением, например.
def plus(self, regex: r"\+", **kwargs):
...
В этом случае аннотация называется "regex", а ее значение является регулярным выражением для соответствия, "\ +", которое является знаком+. Эти аннотированные методы помещаются в подклассы, а не в базовый класс.
Когда метод отправки (...) вызывается в строке, класс находит метод с регулярным выражением аннотации, который соответствует строке и вызывает ее. Вот класс:
import inspect
import re
class RegexMethod:
def __init__(self, method, annotation):
self.method = method
self.name = self.method.__name__
self.order = inspect.getsourcelines(self.method)[1] # The line in the source file
self.regex = self.method.__annotations__[annotation]
def match(self, s):
return re.match(self.regex, s)
# Make it callable
def __call__(self, *args, **kwargs):
return self.method(*args, **kwargs)
def __str__(self):
return str.format("Line: %s, method name: %s, regex: %s" % (self.order, self.name, self.regex))
class RegexDispatcher:
def __init__(self, annotation="regex"):
self.annotation = annotation
# Collect all the methods that have an annotation that matches self.annotation
# For example, methods that have the annotation "regex", which is the default
self.dispatchMethods = [RegexMethod(m[1], self.annotation) for m in
inspect.getmembers(self, predicate=inspect.ismethod) if
(self.annotation in m[1].__annotations__)]
# Be sure to process the dispatch methods in the order they appear in the class!
# This is because the order in which you test regexes is important.
# The most specific patterns must always be tested BEFORE more general ones
# otherwise they will never match.
self.dispatchMethods.sort(key=lambda m: m.order)
# Finds the FIRST match of s against a RegexMethod in dispatchMethods, calls the RegexMethod and returns
def dispatch(self, s, **kwargs):
for m in self.dispatchMethods:
if m.match(s):
return m(self.annotation, **kwargs)
return None
Чтобы использовать этот класс, подклассируйте его, чтобы создать класс с аннотированными методами. В качестве примера, вот простой RPNCalculator, который наследуется от RegexDispatcher. Методы, подлежащие отправке, являются (конечно) те, которые содержат аннотацию "regex". Метод parent dispatch() вызывается в вызове.
from RegexDispatcher import *
import math
class RPNCalculator(RegexDispatcher):
def __init__(self):
RegexDispatcher.__init__(self)
self.stack = []
def __str__(self):
return str(self.stack)
# Make RPNCalculator objects callable
def __call__(self, expression):
# Calculate the value of expression
for t in expression.split():
self.dispatch(t, token=t)
return self.top() # return the top of the stack
# Stack management
def top(self):
return self.stack[-1] if len(self.stack) > 0 else []
def push(self, x):
return self.stack.append(float(x))
def pop(self, n=1):
return self.stack.pop() if n == 1 else [self.stack.pop() for n in range(n)]
# Handle numbers
def number(self, regex: r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?", **kwargs):
self.stack.append(float(kwargs['token']))
# Binary operators
def plus(self, regex: r"\+", **kwargs):
a, b = self.pop(2)
self.push(b + a)
def minus(self, regex: r"\-", **kwargs):
a, b = self.pop(2)
self.push(b - a)
def multiply(self, regex: r"\*", **kwargs):
a, b = self.pop(2)
self.push(b * a)
def divide(self, regex: r"\/", **kwargs):
a, b = self.pop(2)
self.push(b / a)
def pow(self, regex: r"exp", **kwargs):
a, b = self.pop(2)
self.push(a ** b)
def logN(self, regex: r"logN", **kwargs):
a, b = self.pop(2)
self.push(math.log(a,b))
# Unary operators
def neg(self, regex: r"neg", **kwargs):
self.push(-self.pop())
def sqrt(self, regex: r"sqrt", **kwargs):
self.push(math.sqrt(self.pop()))
def log2(self, regex: r"log2", **kwargs):
self.push(math.log2(self.pop()))
def log10(self, regex: r"log10", **kwargs):
self.push(math.log10(self.pop()))
def pi(self, regex: r"pi", **kwargs):
self.push(math.pi)
def e(self, regex: r"e", **kwargs):
self.push(math.e)
def deg(self, regex: r"deg", **kwargs):
self.push(math.degrees(self.pop()))
def rad(self, regex: r"rad", **kwargs):
self.push(math.radians(self.pop()))
# Whole stack operators
def cls(self, regex: r"c", **kwargs):
self.stack=[]
def sum(self, regex: r"sum", **kwargs):
self.stack=[math.fsum(self.stack)]
if __name__ == '__main__':
calc = RPNCalculator()
print(calc('2 2 exp 3 + neg'))
print(calc('c 1 2 3 4 5 sum 2 * 2 / pi'))
print(calc('pi 2 * deg'))
print(calc('2 2 logN'))
Мне нравится это решение, потому что нет отдельных таблиц поиска. Регулярное выражение для соответствия встроено в метод, который будет вызываться как аннотация. Для меня это так и должно быть. Было бы неплохо, если бы Python допускал более гибкие аннотации, потому что я предпочел бы добавить комментарий к методу регулярного выражения только самому методу, а не вставлять его в список параметров метода. Однако в настоящий момент это невозможно.
Для интереса взгляните на язык Вольфрама, в котором функции являются полиморфными на произвольных шаблонах, а не только на типах аргументов. Функция, которая является полиморфной в регулярном выражении, является очень мощной идеей, но мы не можем получить ее в Python. Класс RegexDispatcher - лучшее, что я мог сделать.
Ответ 14
Начиная с Python 3.8
и введением выражений присваивания (PEP 572) (:=
оператор), теперь мы можем зафиксировать значение условия re.search(pattern, text)
в match
переменной, чтобы проверить, является ли оно значением None
а затем повторно использовать его в теле условия:
if match := re.search(r'foo(.+)', text):
# do something with match.group(1)
elif match := re.search(r'bar(.+)', text):
# do something with match.group(1)
elif match := re.search(r'baz(.+)', text)
# do something with match.group(2)