Нумерованные и именованные группы, non-capturing группы, backreferences
Группы позволяют выделять части совпадения для последующего извлечения или использования. Это мощный инструмент для структурирования данных.
Группы создаются с помощью круглых скобок ():
import re
# Простая группа
match = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-09')
match.groups() # ('2024', '03', '09')
match.group(0) # '2024-03-09' — всё совпадение
match.group(1) # '2024' — первая группа
match.group(2) # '03' — вторая группа
match.group(3) # '09' — третья группаГруппы нумеруются слева направо по открывающим скобкам:
# Вложенные группы
match = re.search(r'((\w+)\s+(\w+))', 'Hello World')
match.group(0) # 'Hello World' — всё совпадение
match.group(1) # 'Hello World' — первая группа (внешняя)
match.group(2) # 'Hello' — вторая группа
match.group(3) # 'World' — третья группаИменованные группы позволяют обращаться по имени вместо номера:
# Синтаксис: (?P<имя>паттерн)
match = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2024-03-09')
match.group('year') # '2024'
match.group('month') # '03'
match.group('day') # '09'
match.groupdict() # {'year': '2024', 'month': '03', 'day': '09'}# Более читаемый код
pattern = r'(?P<email>\w+@\w+\.\w+):(?P<phone>\d+)'
match = re.search(pattern, 'user@example.com:123456')
email = match.group('email') # Понятно, что извлекаем
phone = match.group('phone') # Не нужно помнить номера группГруппы ?: группируют элементы, но не захватывают совпадение:
# Обычная группа — захватывает
re.search(r'(ab)+', 'abab') # group(1) = 'ab'
# Non-capturing группа — не захватывает
re.search(r'(?:ab)+', 'abab') # group(1) вызовет ошибку — группы нет
# Практический пример: протокол URL
re.search(r'(https?|ftp)://', 'https://example.com') # group(1) = 'https'
re.search(r'(?:https?|ftp)://', 'https://example.com') # Нет группы, толькосоответствуетПрименение квантификатора к группе:
# Повторение группы протоколов
re.search(r'(?:https?|ftp)://\w+', 'https://example.com')Альтернатива без захвата:
# Mr, Mrs, Ms без захвата
re.search(r'(?:Mr|Mrs|Ms)\. \w+', 'Mr. Smith')Улучшение производительности: Non-capturing группы немного быстрее, так как не сохраняют совпадение.
Используйте захваченные группы в том же паттерне:
# Поиск повторяющихся слов
re.search(r'\b(\w+)\s+\1\b', 'hello hello world') # 'hello hello'
# \1 ссылается на первую группу# Синтаксис: (?P=name)
re.search(r'\b(?P<word>\w+)\s+(?P=word)\b', 'hello hello') # 'hello hello'# Поиск одинарных кавычек
re.search(r'"[^"]*"', '"hello"') # '"hello"'
# Поиск парных тегов HTML
re.search(r'<(\w+)>(.*?)</\1>', '<div>content</div>') # '<div>content</div>'
# \1 ссылается на название тега из первой группы
# Удвоение символов
re.search(r'(\w)\1', 'book') # 'oo'
re.search(r'(\w)\1', 'look') # 'oo'# Без групп — список полных совпадений
re.findall(r'\d+-\d+', '1-2 3-4') # ['1-2', '3-4']
# С группами — список кортежей групп
re.findall(r'(\d+)-(\d+)', '1-2 3-4') # [('1', '2'), ('3', '4')]
# С именованными группами — тоже кортежи
re.findall(r'(?P<a>\d+)-(?P<b>\d+)', '1-2 3-4') # [('1', '2'), ('3', '4')]text = '2024-03-09, 2023-01-15, 2022-12-31'
for match in re.finditer(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', text):
print(f"{match.group('day')}.{match.group('month')}.{match.group('year')}")
# 09.03.2024
# 15.01.2023
# 31.12.2022# Замена формата даты
text = '2024-03-09'
re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3.\2.\1', text) # '09.03.2024'
# С именованными группами
re.sub(r'(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})', r'\g<d>.\g<m>.\g<y>', text)def replace_date(match):
year, month, day = match.groups()
return f'{day}.{month}.{year}'
re.sub(r'(\d{4})-(\d{2})-(\d{2})', replace_date, '2024-03-09') # '09.03.2024'text = 'Иванов Иван'
match = re.search(r'(?P<lastname>\w+)\s+(?P<firstname>\w+)', text)
lastname = match.group('lastname') # 'Иванов'
firstname = match.group('firstname') # 'Иван'html = '<a href="https://example.com">Example</a>'
match = re.search(r'<a\s+href="(?P<url>[^"]+)">(?P<text>[^<]+)</a>', html)
url = match.group('url') # 'https://example.com'
text = match.group('text') # 'Example'# Запретить повторяющиеся символы в пароле
def has_no_repeats(password):
return not re.search(r'(\w)\1', password)
has_no_repeats('abc123') # True
has_no_repeats('aabc123') # False — 'aa'phone = '+7 (999) 123-45-67'
# Извлечение цифр
digits = re.sub(r'\D', '', phone) # '79991234567'
# Форматирование
match = re.search(r'(\d)(\d{3})(\d{3})(\d{2})(\d{2})', digits)
if match:
formatted = '+{} ({}) {}-{}-{}'.format(*match.groups())
# '+7 (999) 123-45-67'# Вложенные группы нумеруются по порядку открывающих скобок
pattern = r'((\w+)\s+(\w+))'
match = re.search(pattern, 'Hello World')
match.group(0) # 'Hello World' — всё совпадение
match.group(1) # 'Hello World' — первая группа (внешняя)
match.group(2) # 'Hello' — вторая группа
match.group(3) # 'World' — третья группаmatch = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-09')
match.group(0) # '2024-03-09' — всё совпадение
match.group(1) # '2024' — первая группа
match.group(4) # IndexError — всего 3 группы# Хотим получить все числа
re.findall(r'\d+-\d+', '1-2 3-4') # ['1-2', '3-4'] — правильно
# Но с группами получаем кортежи
re.findall(r'(\d+)-(\d+)', '1-2 3-4') # [('1', '2'), ('3', '4')]
# Решение: non-capturing группа или обработка кортежей
re.findall(r'(?:\d+)-(\d+)', '1-2 3-4') # ['2', '4'] — только вторая цифра# \1 работает только внутри patтерна
re.sub(r'(\d+)', r'\1', '123') # '123' — правильно
# Но в findall \1 не работает
re.findall(r'(\d+)\1', '123123') # ['123'] — ищет повторяющиеся цифры() захватывают совпадение для извлеченияgroup(1), group(2), ...(?P<name>...), group('name')(?:...) не захватывают, только группируют\1, \2 или (?P=name) ссылаются на захваченные группыfindall с группами возвращает кортежи, а не полные совпаденияsub может использовать \1, \2 или \g<name> для заменыВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.