re.match, re.search, re.findall и re.fullmatch: как выбрать метод
Четыре метода поиска на одной и той же строке дают четыре разных результата. Разбираем, где match останавливается, зачем нужен fullmatch и когда компилировать шаблон.
Редакция Питоники
У модуля re нет одного «главного» метода поиска — их четыре, и путаница между ними родила половину вопросов на Stack Overflow. Новичок пишет re.match, ожидает поиск по всей строке, получает None и решает, что шаблон не работает. На самом деле шаблон в порядке: не работает выбранное правило поиска.
В этом уроке мы прогоним одни и те же данные через match, search, fullmatch и findall, сравним результаты, познакомимся с finditer — и выведем простое правило, какой метод брать под какую задачу. Группы, которые вырезают куски находки, тебе уже знакомы из прошлого урока — сегодня они появятся снова, но в главной роли методы.
Чем re.match отличается от re.search?
match смотрит только на начало строки, search ищет по всей строке и возвращает первое совпадение. Больше никакой разницы: шаблон, строка, результат — тип Match у обоих. Разница только в том, где методам разрешено начать совпадение. Проверим на строке, где число сидит в середине.
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 выражает это намерение точнее.
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 — самый строгий метод из троицы: шаблон должен покрыть строку целиком, от первого до последнего символа. Лишний символ или пробел в конце — и совпадения нет. Это готовый инструмент валидации: проверить, что ввод пользователя — целиком номер, целиком дата, целиком код.
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 с якорями, и в уроке про якоря и границы слова мы сравним оба способа честно, включая одну ловушку, где они различаются.
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 — с группами, позициями и всей прочей информацией.
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 — и вот что происходит, если позвать его вслепую.
import re
m = re.match(r"\d+", "заказ 7381")
print(m.group()) # match вернул None, а у None нет .group()
AttributeError: 'NoneType' object has no attribute '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
Схема «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("кот пёс"))
1. Где re.match ищет совпадение?
2. Что вернёт re.search(r"\d+", "abc123")?
3. Что вернёт re.fullmatch(r"\d+", "123abc")?
4. Чем re.finditer отличается от re.findall?
5. Когда стоит выносить шаблон в re.compile?
В редакторе — три строки с числами. Перебери числа через re.finditer, для каждого выведи его текст и кортеж позиций (span), а в конце посчитай сумму всех чисел. Формат вывода — из заготовки.
Чем 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-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
re · Урок 5
Группы в регулярных выражениях: скобки, findall и имена (?P<name>)
Скобки в шаблоне вырезают из находки нужный кусок, а findall с несколькими группами возвращает кортежи — самый неожиданный поворот модуля re. Разбираем группы по номерам, по именам и без захвата.
re · Урок 7
re.sub и re.split: замена и разрезание текста по шаблону
Замена по шаблону со ссылками на группы, функция вместо строки замены и разрезание строки по любому разделителю — два инструмента модуля re, которые заменяют десятки строк с if.
re · Урок 12
Проект: парсер логов сервера на регулярных выражениях
Финальный проект раздела: из сырого лога сервера — к кодам ответов, топу путей, часам пик и самым медленным запросам. Именованные группы, один скомпилированный шаблон и разговор о катастрофическом бэктрекинге.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
BeautifulSoup / Scrapy · Урок 3
Поиск элементов: find, find_all и CSS-селекторы в BeautifulSoup
Два метода find и find_all плюс семейство select закрывают большинство задач поиска. Разбираем их отличия, ловушки с class_ и attrs и применяем к каталогу товаров.
beautifulsoup find find_allfind и find_all в beautifulsoup разница
NumPy · Урок 1
Что такое NumPy и как установить через pip: первый массив ndarray
Первый массив ndarray: создаём, сравниваем со списком, разбираем dtype и shape — и ускоряем сумму миллиона чисел примерно в сто раз.
чем ndarray отличается от списка pythonдля чего нужен numpy
re · Урок 1
Регулярные выражения Python с нуля: первый шаблон и модуль re
Первый шаблон, который находит любую дату в тексте, а не одну конкретную: import re, re.search, raw-строки и объект Match — всё, чтобы перестать бояться регулярки.
регулярные выражения pythonрегулярные выражения с нуля