Получение захваченной группы в одну строку
Существует известный "шаблон", чтобы получить захваченное значение группы или пустую строку, если нет совпадения:
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 ''
# ''