1. Группы и функции


Как скобки () меняют результат findall и finditer. Ловушки и приёмы.

01 Главная ловушка findall

Ты уже знаешь, findall возвращает список всех совпадений. Но стоит добавить скобки () в паттерн, и результат полностью меняется.

Правило, которое ломает мозг Если в паттерне есть захватывающие группы (...), то findall возвращает не полные совпадения, а только содержимое групп!
паттерн без () findall список строк (полные совпадения)
паттерн с () findall список строк/кортежей (только группы!)
Тренажёр · findall: без групп vs с группами
Текст
Паттерн — переключай кнопки:
 
🟢 Полные совпадения
 
🟣 Результат findall
 
 
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 без захвата
Текст
Паттерн
 
🟢 Полные совпадения
 
🟣 findall вернёт
 
 
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):
 
findall вернул бы
 
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 подставляют содержимое соответствующих групп.

Тренажёр · sub с обратными ссылками
Текст
Найти (паттерн)
Заменить на (можно использовать \1, \2, \3)
Результат:
 

💡 В тренажёре JS: $1, $2, $3 — то же самое, что \1, \2, \3 в Python

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-объекты:
 

time 1000 ms
memory 256 Mb

Комментарий учителя