Получение захваченной группы в одну строку

Существует известный "шаблон", чтобы получить захваченное значение группы или пустую строку, если нет совпадения:

match = re.search('regex', 'text')
if match:
    value = match.group(1)
else:
    value = ""

или

match = re.search('regex', 'text')
value = match.group(1) if match else ''

Есть ли простой и питонический способ сделать это в одной строке?

Другими словами, могу ли я предоставить значение по умолчанию для группы захвата в случае, если она не найдена?


Например, мне нужно извлечь все буквенно-цифровые символы (и _) из текста после строки key=:

>>> import re
>>> PATTERN = re.compile('key=(\w+)')
>>> def find_text(text):
...     match = PATTERN.search(text)
...     return match.group(1) if match else ''
... 
>>> find_text('foo=bar,key=value,beer=pub')
'value'
>>> find_text('no match here')
''

Возможно ли, что find_text() является однострочным?

Это просто пример, я ищу общий подход.

Ответ 1

Цитирование из документов MatchObjects,

Объекты соответствия всегда имеют логическое значение True. Поскольку match() и search() возвращают None при отсутствии совпадения, вы можете проверить, было ли совпадение, с помощью простого оператора if:

match = re.search(pattern, string)
if match:
   process(match)

Поскольку другого варианта нет, и поскольку вы используете функцию, я бы хотел представить эту альтернативу.

def find_text(text, matches = lambda x: x.group(1) if x else ''):
    return matches(PATTERN.search(text))

assert find_text('foo=bar,key=value,beer=pub') == 'value'
assert find_text('no match here') == ''

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

Думая о решении @Kevin и предложениях @devnull в комментариях, вы можете сделать что-то вроде этого

def find_text(text):
    return next((item.group(1) for item in PATTERN.finditer(text)), "")

Это использует тот факт, что next принимает значение по умолчанию, которое будет возвращено в качестве аргумента. Но это накладные расходы на создание выражения генератора на каждой итерации. Итак, я бы придерживался первой версии.

Ответ 2

Вы можете играть с шаблоном, используя пустую альтернативу в конце строки в группе захвата:

>>> re.search(r'((?<=key=)\w+|$)', 'foo=bar,key=value').group(1)
'value'
>>> re.search(r'((?<=key=)\w+|$)', 'no match here').group(1)
''

Ответ 3

Можно ссылаться на результат вызова функции дважды в одном однострочном пространстве: создать лямбда-выражение и вызвать функцию в аргументах.

value = (lambda match: match.group(1) if match else '')(re.search(regex,text))

Однако я не считаю это особенно читаемым. Код ответственно - если вы собираетесь писать хитрый код, оставьте описательный комментарий!

Ответ 4

Re: "Есть ли простой и питонический способ сделать это в одной строке?" Ответ нет. Любые способы заставить это работать в одной строке (без определения вашей собственной оболочки), будут более уродливыми, чем те, которые вы уже представили. Но определение вашей собственной оболочки отлично Pythonic, так как использует две вполне читаемые строки, а не одну трудную для чтения строку.

Ответ 5

Однострочная версия:

if re.findall(pattern,string): pass

Проблема здесь в том, что вы хотите подготовиться к нескольким совпадениям или убедиться, что ваш шаблон только ударил один раз. Расширенная версия:

# matches is a list
matches = re.findall(pattern,string)

# condition on the list fails when list is empty
if matches:
    pass

Итак, для вашего примера "извлеките все буквенно-цифровые символы (и _) из текста после клавиши = строка":

# Returns 
def find_text(text):
    return re.findall("(?<=key=)[a-zA-Z0-9_]*",text)[0]

Ответ 6

Один лайнер, один вкладыш... Почему вы не можете записать его на 2 строках?

getattr(re.search('regex', 'text'), 'group', lambda x: '')(1)

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

Ответ 7

Одна строка для вас, хотя и не совсем Pythonic.

find_text = lambda text: (lambda m: m and m.group(1) or '')(PATTERN.search(text))

Действительно, на языке программирования Схемы все локальные конструкторы переменных могут быть получены из приложений лямбда-функций.

Ответ 8

Вы можете сделать это как:

value = re.search('regex', 'text').group(1) if re.search('regex', 'text') else ''

Хотя это не очень эффективно, учитывая тот факт, что вы дважды запускаете регулярное выражение.

Или запустить его только один раз, когда предложил @Kevin:

value = (lambda match: match.group(1) if match else '')(re.search(regex,text))

Ответ 9

Начиная с Python 3.8 и введением выражений присваивания (PEP 572) (:= оператор), мы можем назвать поисковое выражение регулярного выражения pattern.search(text), чтобы оба проверить, есть ли совпадение (как pattern.search(text) возвращает либо None либо объект re.Match) и использует его для извлечения соответствующей группы:

# pattern = re.compile(r'key=(\w+)')
match.group(1) if (match := pattern.search('foo=bar,key=value,beer=pub')) else ''
# 'value'
match.group(1) if (match := pattern.search('no match here')) else ''
# ''