Регулярные выражения в Python работают через встроенный модуль re. Подключите его строкой import re, запишите шаблон в r-строке и вызовите нужную функцию: re.search() найдёт первое совпадение, re.findall() соберёт все, re.sub() заменит найденное, re.split() разрежет строку. Например, re.findall(r"\d+", text) вытащит из текста все числа.
Ставить ничего не нужно: модуль входит в стандартную библиотеку. Если вы только знакомитесь с языком, сначала загляните в обзор языка Python и первые простые программы, а потом возвращайтесь сюда. Пройти язык целиком, с практикой и проверкой заданий, помогут курсы по Python. Ниже шпаргалка по синтаксису, все функции модуля с выводом интерпретатора, флаги, готовые шаблоны для частых задач и частые ошибки новичков.
Как подключить модуль re и найти первое совпадение

Регулярное выражение — это шаблон, которому должна соответствовать строка. В шаблоне \d+ символ \d значит «любая цифра», а + значит «один раз или больше». Вместе это «число из одной или нескольких цифр».
- Импортируйте модуль:
import re. - Запишите шаблон в сырой строке с префиксом
r:r"\d+". - Вызовите функцию поиска и передайте ей шаблон и текст.
- Проверьте результат: функция вернёт объект
MatchилиNone, если ничего не нашлось.
import re
text = "Заказ №4512 оплачен 14.10.2026"
m = re.search(r"\d+", text)
print(m)
print(m.group(), m.start(), m.end())
# <re.Match object; span=(7, 11), match='4512'>
# 4512 7 11
В объекте Match лежит найденный фрагмент (group()) и его позиция в строке: start() и end(). Конец отсчитывается как в срезах, то есть text[7:11] даст ту же подстроку '4512'.
Зачем нужна r-строка
В обычной строке Python сам обрабатывает обратную косую черту. Последовательность "\b" превращается в один служебный символ backspace, и до модуля re доходит уже он, а не граница слова.
import re
print(len("\b"), len(r"\b"))
print(re.findall("\bкот\b", "кот и котлета"))
print(re.findall(r"\bкот\b", "кот и котлета"))
# 1 2
# []
# ['кот']
Без префикса r шаблон молча не нашёл ни одного «кота». Пишите все регулярные выражения в r-строках, даже если в шаблоне пока нет обратной черты: так вы не получите сюрприз, когда добавите \b или \d.
Синтаксис регулярных выражений: шпаргалка
Шаблон собирается из обычных символов, которые ищутся как есть, и метасимволов со специальным смыслом. Полный список есть в разделе о синтаксисе в документации Python, а ниже то, что нужно в повседневной работе.
Символы и классы
| Запись | Что означает | Пример |
|---|---|---|
. |
любой символ, кроме перевода строки | к.т найдёт «кот», «кит» |
\d / \D |
цифра / не цифра | \d\d найдёт «42» |
\w / \W |
буква, цифра или _ / всё остальное |
\w+ найдёт слово целиком |
\s / \S |
пробельный символ / не пробельный | \s+ найдёт серию пробелов |
[abc] |
один символ из набора | [,;] найдёт запятую или точку с запятой |
[^abc] |
любой символ, кроме перечисленных | [^0-9] то же, что \D |
[а-яё] |
диапазон символов | строчная русская буква |
\., \+, \( |
сам символ, а не его спецзначение | \d+\.\d+ найдёт «3.14» |
Квантификаторы
| Запись | Сколько раз | Пример |
|---|---|---|
* |
0 и больше | \d* подойдёт и пустой строке |
+ |
1 и больше | \d+ число любой длины |
? |
0 или 1 | руб\.? найдёт «руб» и «руб.» |
{n} |
ровно n | \d{6} индекс из шести цифр |
{n,m} |
от n до m | \d{2,4} от двух до четырёх цифр |
*?, +? |
как можно меньше (ленивый вариант) | <.+?> один тег, а не весь абзац |
Якоря и группы
| Запись | Что делает |
|---|---|
^ / $ |
начало / конец строки (с флагом re.MULTILINE — каждой строки текста) |
\b |
граница слова: \bкот\b не зацепит «котлету» |
(...) |
группа: часть совпадения, которую можно достать отдельно |
(?P<name>...) |
именованная группа |
(?:...) |
группа без сохранения, только для объединения |
a|b |
или a, или b |
(?=...) |
проверка «дальше идёт …», в совпадение не входит |
Функции модуля re: что выбрать под задачу
Функций в модуле немного, и путают их в основном по тому, что они возвращают. Сводка:
| Функция | Что ищет | Что возвращает |
|---|---|---|
re.search() |
первое совпадение в любом месте строки | Match или None |
re.match() |
совпадение только с начала строки | Match или None |
re.fullmatch() |
совпадение всей строки целиком | Match или None |
re.findall() |
все совпадения | список строк или кортежей |
re.finditer() |
все совпадения | итератор объектов Match |
re.sub() / re.subn() |
замена по шаблону | новая строка / кортеж «строка, число замен» |
re.split() |
разбиение по шаблону | список строк |
re.compile() |
ничего, готовит шаблон | объект Pattern с теми же методами |
re.search, re.match и re.fullmatch
Главная ловушка модуля: re.match() ищет совпадение только в начале строки. Если число стоит в середине, match вернёт None, а search его найдёт.
import re
s = "Цена 990 руб., скидка 15%"
print(re.search(r"\d+", s).group())
print(re.match(r"\d+", s))
print(re.match(r"Цена", s).group())
print(re.fullmatch(r"\d{6}", "123456"))
print(re.fullmatch(r"\d{6}", "1234567"))
# 990
# None
# Цена
# <re.Match object; span=(0, 6), match='123456'>
# None
Для проверки формата (индекс, код из СМС, номер заказа) берите re.fullmatch(): она требует, чтобы шаблону соответствовала вся строка, и седьмая лишняя цифра уже не пройдёт.
В Python 3.15 у
re.match()появилось второе имя,re.prefixmatch(), а старое помечено как мягко устаревшее. Разработчики объясняют это тем, что в других языках словом match называют поиск в любом месте строки, то есть то, что в Python делаетsearch(). Удалятьmatch()не собираются: так сказано в документации модуля re.
Практический вывод такой. Если код должен работать и на 3.14, и на более старых версиях, оставляйте match(). Если проект живёт только на Python 3.15 и новее, prefixmatch() читается понятнее. Как обновить интерпретатор, есть в инструкции по установке Python.
re.findall и re.finditer
findall() сразу отдаёт список найденных строк, это самый короткий путь «вытащить всё». finditer() нужен, когда кроме текста важна позиция или нужны группы каждого совпадения: он возвращает объекты Match по одному и не собирает весь список в памяти.
import re
log = "ERROR 12:01 диск; WARN 12:05 память; ERROR 12:09 сеть"
print(re.findall(r"\d\d:\d\d", log))
for m in re.finditer(r"ERROR (\d\d:\d\d)", log):
print(m.group(1), m.span())
# ['12:01', '12:05', '12:09']
# 12:01 (0, 11)
# 12:09 (37, 48)
re.sub и re.subn: замена по шаблону
Функция re.sub(шаблон, замена, строка) возвращает новую строку. В замене можно сослаться на группы шаблона как \1, \2 и так далее, а вместо строки можно передать функцию: она получит каждый Match и вернёт текст для подстановки.
import re
phone = "Звоните: +7 (912) 345-67-89"
print(re.sub(r"\D", "", phone))
print(re.sub(r"(\d{2})\.(\d{2})\.(\d{4})", r"\3-\2-\1", "Срок до 31.12.2026"))
def double(m):
return str(int(m.group()) * 2)
print(re.sub(r"\d+", double, "Цены: 100 и 250"))
print(re.subn(r"\s+", " ", "много лишних пробелов"))
# 79123456789
# Срок до 2026-12-31
# Цены: 200 и 500
# ('много лишних пробелов', 2)
re.subn() делает то же самое и дополнительно сообщает, сколько замен случилось. Посимвольные замены можно потренировать на шифре Цезаря: там тоже приходится обходить каждый символ строки и решать, чем его заменить.
re.split: разбить строку по нескольким разделителям
Обычный str.split() режет строку по одному разделителю. re.split() принимает шаблон, поэтому справляется со смесью запятых, точек с запятой и пробелов за один вызов.
import re
print(re.split(r"[,;\s]+", "python, java;go rust"))
print(re.split(r"(-)", "2026-10-15"))
print(re.split(r"\s", "a b c d", maxsplit=2))
# ['python', 'java', 'go', 'rust']
# ['2026', '-', '10', '-', '15']
# ['a', 'b', 'c d']
Если поставить разделитель в скобки, он останется в результате. Аргумент maxsplit ограничивает число разрезов; передавайте его по имени, потому что позиционная передача maxsplit и flags объявлена устаревшей с Python 3.13.
re.compile: один шаблон на много строк
re.compile() превращает шаблон в объект Pattern, у которого есть те же search(), findall(), sub() и другие методы. Он пригодится, когда одна регулярка нужна в цикле или в нескольких функциях.
import re
date_re = re.compile(r"(\d{2})\.(\d{2})\.(\d{4})")
for line in ["старт 01.09.2026", "без даты", "финиш 25.12.2026"]:
m = date_re.search(line)
if m:
print(m.groups())
print(date_re.pattern)
# ('01', '09', '2026')
# ('25', '12', '2026')
# (\d{2})\.(\d{2})\.(\d{4})
Ради скорости компилировать шаблоны не обязательно. Модуль сам кеширует последние использованные шаблоны, и программам, где одновременно работает несколько регулярок, по словам документации, можно не беспокоиться о компиляции. Главный выигрыш в читаемости: у шаблона появляется имя.
Группы и объект Match
Скобки в шаблоне выделяют части совпадения. Метод group(0) возвращает совпадение целиком, group(1) первую группу, groups() все группы кортежем. Именованные группы (?P<имя>...) избавляют от подсчёта скобок.
import re
m = re.search(r"(?P<user>[\w.]+)@(?P<domain>[\w.-]+)", "пишите на ivan.petrov@mail.ru")
print(m.group(0))
print(m.group("user"), m.group(2))
print(m.groupdict())
# ivan.petrov@mail.ru
# ivan.petrov mail.ru
# {'user': 'ivan.petrov', 'domain': 'mail.ru'}
У именованной группы остаётся и номер, поэтому m.group(2) и m.group("domain") вернут одно и то же. groupdict() собирает все именованные группы в словарь, его можно сразу передать дальше в код.
Как группы меняют результат findall
Здесь новички чаще всего получают «не тот» результат. Без групп findall() возвращает совпадения целиком, с одной группой только её содержимое, а с несколькими группами список кортежей.
import re
text = "a1=10, b2=20"
print(re.findall(r"\w\d=\d+", text))
print(re.findall(r"(\w)\d=(\d+)", text))
print(re.findall(r"(?:\w)\d=(\d+)", text))
# ['a1=10', 'b2=20']
# [('a', '10'), ('b', '20')]
# ['10', '20']
- Нужен весь фрагмент: уберите скобки или оберните весь шаблон в одну группу.
- Скобки нужны только для
|или квантификатора: пишите(?:...), и группа не попадёт в результат.
Флаги: re.IGNORECASE, re.MULTILINE, re.DOTALL и другие
Флаг передаётся последним аргументом и меняет поведение шаблона. Несколько флагов объединяются побитовым оператором |, например re.I | re.M.
| Флаг | Короткое имя | Что меняет |
|---|---|---|
re.IGNORECASE |
re.I |
регистр букв не важен |
re.MULTILINE |
re.M |
^ и $ работают на каждой строке текста |
re.DOTALL |
re.S |
точка совпадает и с переводом строки |
re.VERBOSE |
re.X |
в шаблоне можно ставить пробелы и комментарии |
re.ASCII |
re.A |
\w, \d, \s понимают только латиницу и ASCII-цифры |
import re
print(re.findall(r"python", "Python и PYTHON", re.IGNORECASE))
text = "первая\nвторая\nтретья"
print(re.findall(r"^\w+", text))
print(re.findall(r"^\w+", text, re.MULTILINE))
print(re.search(r"<b>.+</b>", "<b>жирный\nтекст</b>"))
print(re.search(r"<b>.+</b>", "<b>жирный\nтекст</b>", re.DOTALL).group())
# ['Python', 'PYTHON']
# ['первая']
# ['первая', 'вторая', 'третья']
# None
# <b>жирный
# текст</b>
Без re.MULTILINE якорь ^ нашёл только первое слово всего текста. Без re.DOTALL точка не прошла через перевод строки внутри тега, и search вернул None.
re.VERBOSE: длинный шаблон с комментариями
Длинную регулярку через месяц не прочитает даже автор. С флагом re.VERBOSE пробелы в шаблоне игнорируются, а после # можно писать пояснения, как в обычном коде.
import re
price = re.compile(r"""
(\d+(?:\s\d{3})*) # 45 900: цифры и тройки через пробел
\s? # необязательный пробел
(₽|руб) # валюта
""", re.VERBOSE)
print(price.findall("Курс стоит 45 900 ₽, рассрочка 3 825 руб в месяц"))
# [('45 900', '₽'), ('3 825', 'руб')]
Пробел, который должен быть частью шаблона, в таком режиме пишется явно, через \s или [ ].
Регулярные выражения и кириллица
В Python 3 строки хранятся в Юникоде, и \w по умолчанию ловит русские буквы так же, как латинские. С флагом re.ASCII русские слова из результата пропадают.
import re
print(re.findall(r"\w+", "Привет, world_2026!"))
print(re.findall(r"\w+", "Привет, world_2026!", re.ASCII))
print(re.findall(r"[а-яё]+", "Ёлка и ель", re.IGNORECASE))
print(re.findall(r"[а-я]+", "Ёлка и ель", re.IGNORECASE))
# ['Привет', 'world_2026']
# ['world_2026']
# ['Ёлка', 'и', 'ель']
# ['лка', 'и', 'ель']
Последняя строка показывает частую ошибку: диапазон [а-я] не включает букву «ё», потому что в таблице Юникода она стоит отдельно от остального алфавита. Пишите [а-яё], а для заглавных [А-ЯЁ] или флаг re.IGNORECASE.
Если текст пришёл байтами (из сети или из файла, открытого в режиме "rb"), сначала превратите его в строку методом decode(). Строковый шаблон к байтам не применяется, Python выдаст TypeError: cannot use a string pattern on a bytes-like object. Как байты и текст переходят друг в друга, хорошо видно на примере кодирования Base64.
Жадные и ленивые квантификаторы

Квантификаторы * и + жадные: они захватывают как можно больше символов. На тексте с повторяющимися метками это даёт одно огромное совпадение вместо нескольких маленьких.
import re
html = "<p>один</p><p>два</p>"
print(re.findall(r"<p>.*</p>", html))
print(re.findall(r"<p>.*?</p>", html))
print(re.findall(r"<p>(.*?)</p>", html))
# ['<p>один</p><p>два</p>']
# ['<p>один</p>', '<p>два</p>']
# ['один', 'два']
Знак ? после квантификатора делает его ленивым: .*? остановится на первом подходящем </p>. Сам HTML регулярками лучше не разбирать: вложенные теги и атрибуты быстро ломают любой шаблон, для этого есть парсеры вроде html.parser из стандартной библиотеки.
Примеры регулярных выражений для частых задач
Четыре шаблона, которые пригодятся в обработке анкет, логов и выгрузок. Больше задач для разминки есть в подборке 30 задач по Python для начинающих.
Достать домены из списка email
Якорь $ привязывает группу к концу адреса, а строки без «@» просто отсеиваются.
import re
emails = ["anna@yandex.ru", "oleg@gmail.com", "bad.address", "kate@mail.ru"]
domains = [m.group(1) for e in emails if (m := re.search(r"@([\w.-]+)$", e))]
print(domains)
# ['yandex.ru', 'gmail.com', 'mail.ru']
Привести телефоны к одному формату
Сначала re.sub(r"\D", "", n) оставляет только цифры, потом fullmatch проверяет, что их 11 и номер мобильный. Запись ... if ok else ... в конце называется тернарным оператором.
import re
numbers = ["+7 912 345-67-89", "8 (912) 345 67 89", "912-345-67", "+79123456789"]
for n in numbers:
digits = re.sub(r"\D", "", n)
ok = re.fullmatch(r"[78]9\d{9}", digits)
print(n, "->", "+7" + digits[1:] if ok else "не номер")
# +7 912 345-67-89 -> +79123456789
# 8 (912) 345 67 89 -> +79123456789
# 912-345-67 -> не номер
# +79123456789 -> +79123456789
Проверить сложность пароля
Три проверки (?=...) смотрят вперёд и ничего не съедают: в строке должна быть цифра, заглавная и строчная латинская буква. Длину от восьми символов задаёт .{8,}.
import re
pwd_re = re.compile(r"(?=.*\d)(?=.*[A-Z])(?=.*[a-z]).{8,}")
for p in ["qwerty12", "Qwerty12", "Qw1"]:
print(p, bool(pwd_re.fullmatch(p)))
# qwerty12 False
# Qwerty12 True
# Qw1 False
Искать текст пользователя как есть
Если шаблон собирается из чужой строки (поисковый запрос, имя файла), символы вроде + и скобок сломают регулярку или изменят её смысл. re.escape() экранирует их за вас.
import re
query = "c++ (beta)"
print(re.escape(query))
print(re.findall(re.escape(query), "курсы c++ (beta) и c++ (beta)"))
# c\+\+\ \(beta\)
# ['c++ (beta)', 'c++ (beta)']
Частые ошибки и что с ними делать

Типичные ошибки при работе с модулем re, их причины и решения:
- AttributeError: ‘NoneType’ object has no attribute ‘group’. Поиск ничего не нашёл и вернул
None, а код сразу вызвалgroup(). Проверяйте результат перед использованием:if m:. - re.error: missing ), unterminated subpattern. В шаблоне не закрыта скобка. Позиция в сообщении указывает, где открылась незакрытая группа.
- Шаблон с
\bничего не находит. Забыт префиксr, и Python превратил\bв backspace до того, как шаблон попал в модуль. - match() возвращает None, хотя совпадение в строке есть. Оно не в начале строки. Нужен
search(). - findall() отдаёт кортежи вместо строк. В шаблоне несколько групп. Лишние замените на
(?:...). - Слова с «ё» выпадают из результата. В диапазоне написано
[а-я], нужно[а-яё].
import re
m = re.match(r"\d+", "abc")
print(m)
try:
print(m.group())
except AttributeError as e:
print("AttributeError:", e)
# None
# AttributeError: 'NoneType' object has no attribute 'group'
import re
try:
re.compile(r"(\d+")
except re.error as e:
print("re.error:", e)
# re.error: missing ), unterminated subpattern at position 0
Регулярка зависла: катастрофический перебор
Вложенные квантификаторы вроде (a+)+ на строке, которая почти подходит, заставляют модуль перебирать все способы разбить её на части. Время растёт экспоненциально: каждые два лишних символа примерно вчетверо.
import re, time
for n in (20, 22, 24, 26):
s = "a" * n + "!"
t = time.perf_counter()
re.fullmatch(r"(a+)+", s)
print(n, round(time.perf_counter() - t, 2))
t = time.perf_counter()
re.fullmatch(r"a+", "a" * 26 + "!")
print("a+", round(time.perf_counter() - t, 4))
# 20 0.04
# 22 0.11
# 24 0.36
# 26 1.45
# a+ 0.0001
Секунды на вашем компьютере будут другими, рост тот же. Лечится это упрощением шаблона (здесь хватит a+) или атомарной группой (?>...): выйдя из неё, модуль уже не возвращается внутрь перебирать другие варианты. Атомарные группы и захватывающие квантификаторы вроде a++ появились в Python 3.11.
import re, time
s = "a" * 26 + "!"
t = time.perf_counter()
print(re.fullmatch(r"(?>a+)+", s))
print(round(time.perf_counter() - t, 4))
# None
# 0.0
Как проверить регулярное выражение перед запуском
Отлаживать шаблон в рабочем скрипте долго. Быстрее собрать его отдельно:
- Откройте regex101 и выберите слева диалект Python.
- Вставьте шаблон и несколько строк для проверки, включая те, что не должны совпадать.
- Справа сервис объяснит каждый кусок шаблона и подсветит группы.
- Перенесите готовый шаблон в код в r-строке и прогоните на реальных данных.
Для системного разбора темы у разработчиков Python есть отдельное руководство по регулярным выражениям, а как устроена документация на python.org и как в ней искать, мы разбирали в отдельной статье.
Где освоить Python системно
Регулярные выражения закрывают одну задачу: найти и переделать текст по шаблону. В работе они стоят рядом с чтением файлов, обработкой ошибок, запросами к сайтам и базами данных. Чтобы собрать это в систему, удобнее идти по программе курса с практикой и проверкой кода.
Порядок шагов до первой работы описан в разборе, как стать Python-разработчиком. Программы с ценами и сроками сравнивайте среди курсов по Python или в подборке по профессии Python-разработчик.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 137 011 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| ДО Профессия Python-разработчик Перейти на сайт курса | 149 001 ₽ | 3167 ₽/мес. | 10 месяцев | Обзор курса | |
| Автоматизированное тестирование на Python Перейти на сайт курса | 118 666 ₽ | 4108 ₽/мес. | 9 месяцев | Обзор курса | |
| Программирование на Python ПРО Перейти на сайт курса | 119 600 ₽ | - | 17 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по Python




