Как скобки () меняют результат findall и finditer. Ловушки и приёмы.
01 Главная ловушка findall
Ты уже знаешь, findall возвращает список всех совпадений. Но стоит добавить скобки () в паттерн, и результат полностью меняется.
Правило, которое ломает мозг Если в паттерне есть захватывающие группы (...), то findall возвращает не полные совпадения, а только содержимое групп!
паттерн без () → findall → список строк (полные совпадения)
паттерн с () → findall → список строк/кортежей (только группы!)
Тренажёр · findall: без групп vs с группами
Текст
Паттерн — переключай кнопки:
import re
text = "email: ivan@mail.ru и anna@gmail.com"
# Без групп — полные совпадения
re.findall(r'\w+@\w+\.\w+', text)
# ['ivan@mail.ru', 'anna@gmail.com']
# С ОДНОЙ группой — только содержимое группы
re.findall(r'(\w+)@\w+\.\w+', text)
# ['ivan', 'anna'] ← только имена!
# С ДВУМЯ группами — кортежи
re.findall(r'(\w+)@(\w+\.\w+)', text)
# [('ivan', 'mail.ru'), ('anna', 'gmail.com')]
02 Три режима findall
В зависимости от количества групп findall работает в трёх режимах:
| Групп в паттерне |
findall возвращает |
Пример результата |
| 0 групп |
Список строк (полные совпадения) |
['ivan@mail.ru', 'anna@gmail.com'] |
| 1 группа |
Список строк (содержимое группы) |
['ivan', 'anna'] |
| 2+ группы |
Список кортежей |
[('ivan','mail.ru'), ('anna','gmail.com')] |
Тренажёр · три режима findall
Текст
Паттерн
✅ Когда это полезно? 1 группа — когда нужно извлечь часть совпадения: число без «руб», имя из email. 2+ групп — когда нужно разобрать на части: день, месяц, год из даты.
03 Как избежать ловушки: (?:...)
Иногда скобки нужны только для группировки (например, (AB)+), а не для захвата. Но findall не видит разницы и всё равно вернёт содержимое группы.
Решение: (?:...) — группа без захвата. Она группирует, но не влияет на результат findall.
Тренажёр · захват vs без захвата
Текст
Паттерн
import re
text = "хаха, хахахаха, хахахахахаха"
# ❌ Ловушка: (ха)+ — группа захватывает только ПОСЛЕДНЕЕ повторение
re.findall(r'(ха)+', text)
# ['ха', 'ха', 'ха'] ← все одинаковые!
# ✅ Правильно: (?:ха)+ — без захвата
re.findall(r'(?:ха)+', text)
# ['хаха', 'хахахаха', 'хахахахахаха']
⚠️ Тонкость с повторением группы Когда группа повторяется через + или *, она захватывает только последнее повторение! Поэтому (ха)+ в findall всегда даёт 'ха', а не 'хахаха'.
04 finditer — полный контроль
finditer возвращает Match-объекты, и группы никогда не мешают — у тебя есть доступ ко всему:
| Метод Match |
Что возвращает |
Пример |
.group() или .group(0) |
Полное совпадение |
'ivan@mail.ru' |
.group(1) |
Первая группа |
'ivan' |
.group(2) |
Вторая группа |
'mail.ru' |
.groups() |
Все группы кортежем |
('ivan', 'mail.ru') |
.start(), .end() |
Позиции в строке |
7, 19 |
Тренажёр · finditer с группами
Текст
Паттерн (с группами)
Match-объекты (как в Python):
finditer даёт доступ ко всему
import re
text = "Дата: 25.01.2024, встреча: 14.03.2024"
# findall — только кортежи групп
re.findall(r'(\d{2})\.(\d{2})\.(\d{4})', text)
# [('25', '01', '2024'), ('14', '03', '2024')]
# finditer — полный контроль!
for m in re.finditer(r'(\d{2})\.(\d{2})\.(\d{4})', text):
print(f"Полное: {m.group()}") # 25.01.2024
print(f" День: {m.group(1)}") # 25
print(f" Месяц: {m.group(2)}") # 01
print(f" Год: {m.group(3)}") # 2024
print(f" Позиция: {m.start()}") # 6
💡 Вывод findall с группами теряет полное совпадение. finditer — никогда. Когда нужны и части, и целое — используй finditer.
05 Группы в sub() — обратные ссылки
В re.sub() группы открывают мощную возможность — обратные ссылки. В строке замены \1, \2 подставляют содержимое соответствующих групп.
import re
text = "25.01.2024, 14.03.2024"
# Переформатировать дату: ДД.ММ.ГГГГ → ГГГГ-ММ-ДД
result = re.sub(
r'(\d{2})\.(\d{2})\.(\d{4})',
r'\3-\2-\1', # \1=день, \2=месяц, \3=год
text
)
print(result) # 2024-01-25, 2024-03-14
06 Именованные группы
Вместо номеров можно давать группам имена: (?P<имя>...). Это делает код понятнее.
import re
text = "Дата: 25.01.2024"
pat = r'(?P<day>\d{2})\.(?P<month>\d{2})\.(?P<year>\d{4})'
m = re.search(pat, text)
print(m.group('day')) # '25'
print(m.group('month')) # '01'
print(m.group('year')) # '2024'
print(m.groupdict()) # {'day': '25', 'month': '01', 'year': '2024'}
Тренажёр · именованные группы
Текст
Паттерн (именованные группы в JS: (?<name>...))
Именованные группы:
| Python синтаксис |
JS синтаксис |
Назначение |
(?P<name>...) |
(?<name>...) |
Создать именованную группу |
m.group('name') |
m.groups.name |
Получить значение |
m.groupdict() |
— |
Словарь всех групп |
\g<name> в sub |
$<name> в replace |
Ссылка в замене |
07 Сводная таблица: группы × функции
| Функция |
Без групп |
С группами () |
findall |
Список полных совпадений |
Список групп / кортежей групп |
finditer |
.group() — полное совпадение |
.group() — полное, .group(N) — группа N |
search |
.group() — первое совпадение |
.group() — полное, .group(N) — группа N |
match |
.group() — совпадение от начала |
.group() — полное, .group(N) — группа N |
sub |
Обычная замена |
Можно использовать \1, \2 в замене |
split |
Список частей |
Содержимое групп тоже попадает в список! |
⚠️ Сюрприз от split с группами re.split(r'(\d+)', 'a1b2c') вернёт ['a', '1', 'b', '2', 'c'] — разделители-группы тоже включены!
08 Песочница
Универсальный тестер · группы и функции
Текст
Паттерн
findall вернёт:
finditer — Match-объекты: