Перейти к содержанию

172 уроков, 13 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 6 из 12 Средний 40 мин 120 XP

re.match, re.search, re.findall и re.fullmatch: как выбрать метод

Четыре метода поиска на одной и той же строке дают четыре разных результата. Разбираем, где match останавливается, зачем нужен fullmatch и когда компилировать шаблон.

Редакция Питоники

У модуля re нет одного «главного» метода поиска — их четыре, и путаница между ними родила половину вопросов на Stack Overflow. Новичок пишет re.match, ожидает поиск по всей строке, получает None и решает, что шаблон не работает. На самом деле шаблон в порядке: не работает выбранное правило поиска.

В этом уроке мы прогоним одни и те же данные через match, search, fullmatch и findall, сравним результаты, познакомимся с finditer — и выведем простое правило, какой метод брать под какую задачу. Группы, которые вырезают куски находки, тебе уже знакомы из прошлого урока — сегодня они появятся снова, но в главной роли методы.

match смотрит только на начало строки, search ищет по всей строке и возвращает первое совпадение. Больше никакой разницы: шаблон, строка, результат — тип Match у обоих. Разница только в том, где методам разрешено начать совпадение. Проверим на строке, где число сидит в середине.

match и search на одной строке
import re

text = "Оплата 4500 подтверждена"

print(re.match(r"\d+", text))
print(re.search(r"\d+", text).group())
Вывод
None
4500

re.match(r"\d+", text) вернул None: строка начинается со слова «Оплата», а match проверяет только её начало и дальше не ищет — даже если совпадение есть через пять символов. re.search проверил все позиции строки и нашёл 4500. Вот почему «шаблон не работает» чаще значит «работает match там, где нужен search».

Зачем тогда вообще match, если search строгий младший брат? Для строк, у которых есть начало: команды бота, методы HTTP-запроса, первые слова служебной строки. В таких форматах «не в начале» и «не подходит» — одно и то же, и match выражает это намерение точнее.

match для разбора команд
import re

def route(command):
    if re.match(r"/help", command):
        return "показываю справку"
    if re.match(r"/start", command):
        return "приветствие нового пользователя"
    return "команда не распознана"

print(route("/help без аргументов"))
print(route("напиши /help"))
Вывод
показываю справку
команда не распознана

Первый вызов получил команду, которая начинается с /help, — маршрут сработал. Второй вызов — обычное сообщение, где /help сидит в середине: match не нашёл его в начале и вернул None, как и должно быть. Команда, затерянная в тексте, командой не считается — здесь поведение match ровно то, что нужно.

Что проверяет re.fullmatch?

fullmatch — самый строгий метод из троицы: шаблон должен покрыть строку целиком, от первого до последнего символа. Лишний символ или пробел в конце — и совпадения нет. Это готовый инструмент валидации: проверить, что ввод пользователя — целиком номер, целиком дата, целиком код.

fullmatch как валидация даты
import re

inputs = ["20.09.2026", "20.09.26", "20-09-2026"]

for s in inputs:
    ok = re.fullmatch(r"\d{2}\.\d{2}\.\d{4}", s)
    print(s, "->", "дата" if ok else "не дата")
Вывод
20.09.2026 -> дата
20.09.26 -> не дата
20-09-2026 -> не дата

Шаблон требует ровно две цифры, точку, ещё две цифры, точку, четыре цифры — и ничего besides. «20.09.26» не прошёл: год из двух цифр не влез в \d{4}. Заметь: для валидации не понадобились якоря ^ и $ — fullmatch уже означает «вся строка». То же самое можно выразить через search с якорями, и в уроке про якоря и границы слова мы сравним оба способа честно, включая одну ловушку, где они различаются.

fullmatch против почти подходящих строк
import re

codes = ["7381", "73 81", "7381 "]

for c in codes:
    print(repr(c), "->", bool(re.fullmatch(r"\d{4}", c)))
Вывод
'7381' -> True
'73 81' -> False
'7381 ' -> False

Здесь repr() печатает строку в кавычках — так видно невидимый пробел в третьем коде. fullmatch отверг и разбитый пробелом код, и код с хвостовым пробелом: «почти номер» — это не номер. Для валидации форм такая строгость — подарок: пользовательский ввод приходит с лишними пробелами чаще, чем без. Если хвостовые пробелы всё же считаешь опечаткой, а не ошибкой — сначала s.strip(), потом fullmatch: чистка ввода и его валидация — два отдельных шага, и смешивать их в одном шаблоне не стоит.

Одна строка, четыре метода, четыре результата

Теперь сведём всё в один блок. Строка лога, шаблон, четыре вызова — и четыре разных ответа. Такой сводный пример стоит держать в голове как карту модуля re.

четыре метода на одинаковых данных
import re

text = "Лог: event=login, event=logout"
pattern = r"event=\w+"

print("match:", re.match(pattern, text))

m = re.search(pattern, text)
print("search:", m.group())

print("fullmatch:", re.fullmatch(pattern, text))
print("findall:", re.findall(pattern, text))
Вывод
match: None
search: event=login
fullmatch: None
findall: ['event=login', 'event=logout']

Одна и та же строка, один и тот же шаблон — и ни одного совпадающего ответа. match: None, потому что строка начинается со слова «Лог». search: первое совпадение где угодно. fullmatch: None, потому что совпадение — не вся строка. findall: все совпадения списком. В таблице — шпаргалка целиком.

МетодЧто делаетЧто возвращает
re.matchпроверяет начало строкиMatch или None
re.searchищет первое совпадение по всей строкеMatch или None
re.fullmatchтребует покрыть строку целикомMatch или None
re.findallсобирает все совпадениясписок строк или кортежей групп
re.finditerперебирает все совпаденияитератор объектов Match
  • нужно найти вхождение в тексте — re.search;
  • нужно проверить, что строка начинается с шаблона, — re.match;
  • нужно проверить строку целиком: код, дата, номер — re.fullmatch;
  • нужны все находки списком, без позиций — re.findall;
  • нужны позиции или группы каждой находки — re.finditer.

Чем finditer отличается от findall?

findall отдаёт готовый список — но только содержимое, без позиций: где именно в строке стояло совпадение, он не говорит. К тому же его формат зависит от групп: с двумя группами он вернёт кортежи, как мы разбирали в уроке про группы. finditer решает обе проблемы: он перебирает все совпадения и на каждое отдаёт полноценный объект Match — с группами, позициями и всей прочей информацией.

finditer: содержимое и позиции
import re

text = "цены: 120, 80 и 250 руб."

for m in re.finditer(r"\d+", text):
    print(m.group(), m.span(), m.start(), m.end())
Вывод
120 (6, 9) 6 9
80 (11, 13) 11 13
250 (16, 19) 16 19

У объекта Match четыре главных метода-свойства. m.group() — совпавший текст (а m.group(1) — первая группа). m.start() и m.end() — индексы начала и конца совпадения: начало включается, конец нет, поэтому text[6:9] — это ровно «120». m.span() — пара (start, end) одним кортежем. Позиции нужны чаще, чем кажется: подсветить находку в редакторе, отрезать обработанный кусок, продолжить разбор строки с места, где остановился поиск.

А теперь типичная ошибка, ради которой стояло дойти до этого места. match и search возвращают None, когда совпадения нет. У None нет метода group — и вот что происходит, если позвать его вслепую.

ошибка: group() у None
import re

m = re.match(r"\d+", "заказ 7381")
print(m.group())   # match вернул None, а у None нет .group()
Вывод
AttributeError: 'NoneType' object has no attribute 'group'
Блок не запускается намеренно: он вызывает ошибку. В полном traceback будут ещё две служебные строки, но суть — всегда в последней: NoneType не имеет group.

Когда нужен re.compile?

Каждый вызов re.search(r"...", text) делает две вещи: превращает строку с шаблоном в скомпилированный объект и ищет. Функция re.compile отделяет первый шаг: она один раз строит объект-шаблон, у которого есть свои методы — pattern.search, pattern.findall, pattern.fullmatch — и всё то же самое поведение. Зачем это нужно? Пока шаблон используется один раз — незачем. А когда один и тот же шаблон крутится в цикле по сотням строк, компиляцию выгодно вынести наружу.

шаблон в цикле: компилируем один раз
import re

pattern = re.compile(r"\d+")

pages = ["стр. 12", "стр. 340", "стр. 7"]

total = 0
for page in pages:
    total += int(pattern.search(page).group())

print(total)
print(type(pattern).__name__)
Вывод
359
Pattern

re.compile(r"\d+") вернул объект класса Pattern — шаблон стал переменной, которую приятно назвать именем и передавать в функции. Вызовы pattern.search(page) внутри цикла не тратят время на повторный разбор шаблона. Честная оговорка: модуль re кеширует компиляцию внутри, поэтому на маленьких скриптах ускорения вы не заметите. Настоящая причина компилировать — чистота кода: именованный шаблон вверху файла читается лучше, чем одна и та же строка, размноженная по функции. У объекта Pattern есть и остальные методы, которые ты уже знаешь: pattern.findall, pattern.fullmatch, pattern.sub — полный интерфейс модуля работает и на скомпилированном шаблоне, включая именованные группы.

В связке с finditer и именованными группами компилированный шаблон — это уже маленький парсер. Вот так выглядит его типовая обвязка для файла лога на настоящем диске.

разбор файла лога локально
import re

pattern = re.compile(r"(?P<time>\d{2}:\d{2}:\d{2}) ERROR")

with open("server.log", encoding="utf-8") as f:
    for line in f:
        m = pattern.search(line)
        if m:
            print(m.group("time"))
Вывод
09:12:33
14:40:02
Браузерная песочница не имеет доступа к файлам, поэтому блок не запускается: это шаблон для локального проекта. Логика внутри — та же, что ты тренируешь в runnable-блоках: compile один раз, search в цикле, проверка if m перед group.

Схема «compile → цикл по строкам → search → if m → работа с группами» — скелет почти каждого скрипта, который что-то вытаскивает из текстов. В финальном проекте курса этот скелет обрастёт статистикой и отчётом, а сегодня достаточно его запомнить.

Что дальше

Теперь у тебя есть карта модуля re: search — найти, match — проверить начало, fullmatch — проверить всю строку, findall — собрать список, finditer — перебрать с позициями. Выбор метода перестаёт быть гаданием. Дальше методы работают в полную силу: в следующем уроке замена и разрезание текста — re.sub со ссылками на группы и re.split по шаблону. А после — якоря и границы слова, которые дают match и fullmatch способ выразить «строка должна начинаться здесь» ещё точнее.

Метод поиска — это вопрос к строке: match спрашивает «начинаешься ли ты с этого?», search — «где ты здесь?», fullmatch — «это всё, что у тебя есть?».

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

import re

print(re.match(r"\d+", "abc123"), re.search(r"\d+", "abc123").group())
import re

for m in re.finditer(r"\d+", "a1 b22 c333"):
    print(m.group(), m.start())
import re

pattern = re.compile(r"\w+")
print(pattern.findall("кот пёс"))
Проверь себя
0 / 5

1. Где re.match ищет совпадение?

2. Что вернёт re.search(r"\d+", "abc123")?

3. Что вернёт re.fullmatch(r"\d+", "123abc")?

4. Чем re.finditer отличается от re.findall?

5. Когда стоит выносить шаблон в re.compile?

Карточки терминов
Запомнено: 0 / 6
Практика

В редакторе — три строки с числами. Перебери числа через re.finditer, для каждого выведи его текст и кортеж позиций (span), а в конце посчитай сумму всех чисел. Формат вывода — из заготовки.

practice.py
Вопросы и ответы по уроку

Чем re.match отличается от re.search в Python?

re.match проверяет, начинается ли строка с шаблона, и не ищет дальше начала. re.search сканирует всю строку и возвращает первое совпадение где угодно. На строке «Оплата 4500» шаблон \d+ через match вернёт None, через search — Match с текстом «4500». Если цель — найти вхождение, нужен search.

Что делать, если re.search вернул None?

Проверить результат перед использованием: идиома `if m:` с обращением к m.group() внутри. None — нормальный ответ search и match на строку без совпадений, а вот вызов .group() прямо у None даёт AttributeError: 'NoneType' object has no attribute 'group' — самую частую ошибку новичка в re.

Ускоряет ли re.compile работу с регулярками?

Немного: компиляция шаблона выносится из цикла и не повторяется. Заметный выигрыш появляется на тысячах вызовов одного шаблона; на маленьких скриптах модуль re и так кеширует компиляцию, так что ускорения вы не увидите. Вторая причина компилировать — читаемость: именованный объект Pattern вверху файла понятнее строки-шаблона, размноженной по коду.

Чем re.fullmatch лучше search с якорями ^ и $?

Коротко: fullmatch(r"\d+", s) читается как намерение — «вся строка должна быть числом», и так же надёжно работает. search(r"^\d+$", s) делает почти то же, но у якоря $ есть нюанс: он совпадает и перед финальным переводом строки, поэтому строка «4500\n» пройдёт проверку через search, но провалится через fullmatch. Разбор этой ловушки — в уроке про якоря.

Понравился урок? Сошлитесь на него

«match смотрит только на начало строки, search ищет по всей строке и возвращает первое совпадение.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.