W3docs

Группы в строках Python — Захватывающие группы в Regex

Как работают захватывающие группы в Python regex: group(), groups(), именованные группы, незахватывающие группы и обратные ссылки.

Когда модуль re в Python находит совпадение в строке, он возвращает объект совпадения. Объект совпадения — это не просто ответ «да/нет»: он запоминает каждый подшаблон, обёрнутый в круглые скобки, — такие подшаблоны называются захватывающими группами. Методы .group() и .groups() позволяют извлечь эти захваченные фрагменты.

В этой главе рассмотрено всё необходимое для уверенной работы с группами: нумерованные группы, именованные группы, незахватывающие группы, обратные ссылки в подстановках и типичные ошибки.

Что такое захватывающая группа?

Захватывающая группа — это часть шаблона регулярного выражения, заключённая в обычные круглые скобки (...). При совпадении шаблона Python отдельно сохраняет текст, соответствующий каждой паре скобок, в дополнение к сохранению всего совпадения целиком.

import re

m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')

print(m.group(0))  # 2024-03-15  — the whole match
print(m.group(1))  # 2024        — first group
print(m.group(2))  # 03          — second group
print(m.group(3))  # 15          — third group

Группы нумеруются слева направо начиная с 1, в порядке появления открывающей скобки. group(0) (или просто group() без аргумента) всегда возвращает всё совпадение целиком.

Метод .group()

.group(n) возвращает текст, захваченный n-й группой. Можно передать несколько индексов сразу — тогда метод вернёт кортеж результатов.

import re

m = re.search(r'(\w+)@(\w+)\.(\w+)', '[email protected]')

# Individual groups
print(m.group(1))       # alice
print(m.group(2))       # example
print(m.group(3))       # com

# Multiple at once
print(m.group(1, 3))    # ('alice', 'com')

Если группа присутствует в шаблоне, но не участвовала в совпадении (например, находилась внутри необязательной секции), group(n) возвращает None.

import re

# group(1) is optional — it may not match
m = re.search(r'(\d+)?-(\d+)', 'abc-456')
print(m.group(1))  # None  (the optional \d+ did not match)
print(m.group(2))  # 456

Метод .groups()

.groups() возвращает кортеж, содержащий текст, захваченный каждой группой, по порядку. Это удобный способ получить все группы сразу.

import re

m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m.groups())  # ('2024', '03', '15')

Значение по умолчанию для незафиксированных групп

Если группа не участвовала в совпадении, в кортеже она представлена как None. Можно передать аргумент default, чтобы заменить такие значения None на что-то более удобное.

import re

m = re.search(r'(\d+)?-(\d+)', 'abc-456')
print(m.groups())              # (None, '456')
print(m.groups(default='0'))   # ('0', '456')

Именованные группы с (?P<name>...)

В сложных шаблонах нумерованные группы сложно отслеживать. Именованные группы позволяют присвоить каждой группе понятную метку с помощью синтаксиса (?P<name>...), а затем обращаться к значению по имени, а не по позиции.

import re

pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
m = re.search(pattern, '2024-03-15')

print(m.group('year'))   # 2024
print(m.group('month'))  # 03
print(m.group('day'))    # 15

Именованные группы по-прежнему имеют номер, поэтому к ним можно обращаться любым из двух способов.

Метод .groupdict()

При наличии именованных групп метод .groupdict() возвращает словарь, в котором каждому имени сопоставлен захваченный текст. Это удобно, когда нужно распаковать совпадение в структурированный объект.

import re

m = re.search(
    r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
    '2024-03-15'
)
print(m.groupdict())
# {'year': '2024', 'month': '03', 'day': '15'}

Результат можно сразу использовать:

date_parts = m.groupdict()
print(f"{date_parts['day']}/{date_parts['month']}/{date_parts['year']}")
# 15/03/2024

Незахватывающие группы с (?:...)

Иногда нужно сгруппировать часть шаблона, чтобы применить квантификатор или чередование, но не хочется, чтобы эта группа попадала в результаты совпадения. Для этого используется (?:...)незахватывающая группа.

import re

# Without non-capturing group: both parts appear in groups()
m1 = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m1.groups())  # ('2024', '03', '15')

# Year grouped but not captured — month and day are groups 1 and 2
m2 = re.search(r'(?:\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m2.groups())  # ('03', '15')

Незахватывающие группы — это рекомендуемая практика, когда конкретное подсовпадение не нужно: они сохраняют чистоту индексов групп и не тратят лишнюю память.

Вложенные группы

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

import re

# ((\d{4})-(\d{2}))-(\d{2})
# group 1 = the outer (year-month pair)
# group 2 = year
# group 3 = month
# group 4 = day
m = re.search(r'((\d{4})-(\d{2}))-(\d{2})', '2024-03-15')
print(m.group(1))   # 2024-03
print(m.group(2))   # 2024
print(m.group(3))   # 03
print(m.group(4))   # 15
print(m.groups())   # ('2024-03', '2024', '03', '15')

Группы с re.findall()

Если в шаблоне, переданном в re.findall(), содержится ровно одна захватывающая группа, функция возвращает список строк — по одной на каждое совпадение.

Если захватывающих групп две и более, она возвращает список кортежей.

import re

# One group → list of strings
emails = '[email protected], [email protected]'
usernames = re.findall(r'(\w+)@\w+\.\w+', emails)
print(usernames)  # ['alice', 'bob']

# Two groups → list of tuples
pairs = re.findall(r'(\w+)@(\w+)\.com', emails)
print(pairs)  # [('alice', 'gmail'), ('bob', 'yahoo')]

Группы с re.finditer()

re.finditer() возвращает итератор объектов совпадений, поэтому на каждом из них можно вызывать .group() и .groups() по отдельности. Это наиболее гибкий подход, когда нужны полные детали каждого совпадения.

import re

text = '[email protected], [email protected]'
for m in re.finditer(r'(?P<user>\w+)@(?P<domain>\w+)\.com', text):
    print(m.group('user'), '->', m.group('domain'))
# alice -> gmail
# bob   -> yahoo

Обратные ссылки в re.sub()

Захватывающие группы также используются в обратных ссылках в re.sub(). В строке замены \1, \2, … ссылаются на текст, захваченный группой 1, группой 2 и т. д. Для именованных групп используется \g<name>.

import re

# Swap first and last name
result = re.sub(r'(\w+)\s(\w+)', r'\2 \1', 'John Smith')
print(result)  # Smith John

# Same using named groups
result2 = re.sub(
    r'(?P<first>\w+)\s(?P<last>\w+)',
    r'\g<last> \g<first>',
    'Jane Doe'
)
print(result2)  # Doe Jane

Позиция группы: .start(), .end(), .span()

Объекты совпадений предоставляют позицию каждой группы, а не только её текст. Передайте номер (или имя) группы в .start(), .end() или .span().

import re

m = re.search(r'(\d{4})-(\d{2})', '2024-03')
print(m.span(1))    # (0, 4)  — year occupies indices 0..3
print(m.start(2))   # 5       — month starts at index 5
print(m.end(2))     # 7       — month ends before index 7

Краткий справочник

МетодВозвращаетПримечания
m.group() или m.group(0)Всё совпадение целикомВсегда доступен
m.group(n)Текст группы nNone, если группа не совпала
m.group(n, m, ...)Кортеж группНесколько индексов за один вызов
m.groups()Кортеж всех группНеобязательный default= для несовпавших
m.groupdict()Словарь именованных группСодержит только именованные группы
m.start(n) / m.end(n)Начальный / конечный индекс группы nПередайте 0 для всего совпадения
m.span(n)Кортеж (start, end)Сокращение для обоих значений

Типичные ошибки

Вызов .group() без проверки на None. re.search() возвращает None, если совпадений нет, поэтому прямой вызов .group() на таком результате вызовет AttributeError. Всегда проверяйте результат:

import re

m = re.search(r'(\d+)', 'no digits here')
if m:
    print(m.group(1))
else:
    print('no match')
# no digits here

Путаница между .group() и .groups(). .group(1) возвращает string; .groups() всегда возвращает кортеж. Их смешение приводит к трудноуловимым ошибкам типов.

Забывать, что re.findall() меняет форму результата в зависимости от групп. Без групп функция возвращает плоский список строк; с группами — список кортежей. Добавление или удаление захватывающей группы может незаметно сломать код, обрабатывающий результат.

Когда использовать каждый подход

  • Используйте нумерованные группы для простых коротких шаблонов с небольшим числом групп.
  • Используйте именованные группы, если шаблон сложный или результат будет использоваться в нескольких строках кода — имена служат самодокументацией.
  • Используйте незахватывающие группы (?:...), когда группировка нужна только для применения квантификатора или чередования, а не для извлечения значения.
  • Используйте .groups(), чтобы сразу распаковать все захваченные значения в кортеж.
  • Используйте .groupdict(), когда все группы именованы и нужен словарь.

Для общего обзора модуля re и синтаксиса шаблонов смотрите главу Python RegEx. Для операций со строками, не требующих регулярных выражений, смотрите Modify Strings и Python String Methods.

Практика

Практика
In Python, which functions or methods can be used to define a group within strings?
In Python, which functions or methods can be used to define a group within strings?
Was this page helpful?