Регулярные выражения
Регулярные выражения в Python позволяют искать, извлекать и изменять фрагменты текста по гибким шаблонам. Базовый инструмент — модуль re, который даёт полный набор операций: компиляция шаблона, поиск первого совпадения, поиск всех совпадений, замены с функцией-колбэком, разбиение строки по шаблону, безопасное экранирование. В этом руководстве вы последовательно разберёте синтаксис регулярных выражений: от символов и классов до групп, ссылок, lookaround-проверок и флагов, а затем закрепите материал на практических рецептах и анти-паттернах.
1. Введение: модуль re и ключевые операции
Модуль re входит в стандартную библиотеку Python. Его функции работают либо напрямую со строкой и шаблоном, либо с заранее скомпилированным объектом паттерна.
- re.compile(pattern, flags=0) — компилирует шаблон и возвращает объект паттерна.
- re.search — ищет первое совпадение где угодно в строке.
- re.match — пытается сопоставить с начала строки (эквивалент re.search с якорем начала).
- re.fullmatch — требует совпадение всей строки целиком.
- re.findall — возвращает список совпадений (или кортежей групп).
- re.finditer — даёт итератор по объектам Match (экономит память, удобен для больших текстов).
- re.sub/re.subn — замены по шаблону (вторая также возвращает число замен).
- re.split — разбивает строку по разделителю-шаблону.
- re.escape — экранирует все небуквенно-цифровые символы для безопасного включения в шаблон.
import re
m = re.search(r"d+", "abc123xyz")
print(m.group()) # 123
p = re.compile(r"w+@w+.w+")
print(bool(p.fullmatch("[mail@example.com](mailto:mail@example.com)"))) # True
2. Базовый алфавит и экранирование
Любой «обычный» символ в шаблоне совпадает сам с собой. Спецсимволы («метасимволы») имеют особый смысл и требуют экранирования обратной косой чертой , если вы хотите искать их буквально:
Метасимволы: . ^ $ * + ? { } [ ] | ( )
- . — любой символ, кроме перевода строки (если не указан флаг re.S).
- Чтобы искать точку буквально, используйте ., чтобы искать обратную косую — \.
import re
print(bool(re.search(r".", "3.14"))) # True — точка буквально
print(bool(re.search(r".", "n"))) # False, если нет re.S
Совет: почти всегда пишите шаблоны как «сырые строки» с префиксом r: r»d+». Так Python не съест ваши обратные косые, оставив их для движка регулярных выражений.
3. Классы символов
Квадратные скобки задают набор допустимых символов на позиции:
- [abc] — любой из символов a, b или c.
- [a-z] — любой символ из диапазона a…z.
- [^0-9] — отрицание: любой символ, кроме цифры.
Часто используются предопределённые классы:
- d — цифра; D — не цифра.
- w — «слово» (буква, цифра, подчёркивание, и в Unicode-режиме — буквенные символы национальных алфавитов); W — не «слово».
- s — пробельный символ; S — не пробельный.
import re
print(re.findall(r"[А-ЯЁ][а-яё]+", "Привет, Мир, Ёж")) # ['Привет', 'Мир', 'Ёж']
print(re.findall(r"w+", "alpha_123 бета")) # ['alpha_123', 'бета']
Замечание: диапазоны зависят от текущего режима Unicode/ASCII. Флаг re.A («ASCII») ограничит w, d, s до ASCII-множества.
4. Якоря и границы
- ^ — начало строки; $ — конец строки.
- b — граница слова; B — не граница слова.
- A, Z — начало и конец всей входной строки независимо от re.M.
Флаг re.M (MULTILINE) меняет интерпретацию ^ и $: они совпадают также с началом/концом каждой строки внутри текста.
import re
text = "foonbar"
print(bool(re.search(r"^bar$", text))) # False
print(bool(re.search(r"^bar$", text, re.M))) # True
print(re.findall(r"bcatb", "concatenate cat scat")) # ['cat']
5. Квантификаторы: жадные и ленивые
- * — 0 или более повторений.
- + — 1 или более.
- ? — 0 или 1.
- {m} — ровно m раз; {m,} — m или более; {m,n} — от m до n.
По умолчанию квантификаторы «жадные». Добавьте ?, чтобы сделать их «ленивыми» и брать как можно меньше символов:
import re
s = "one two "
print(re.findall(r".* ", s)) # жадно: один большой матч
print(re.findall(r".*? ", s)) # лениво: два отдельных тега
Важно: ленивый квантификатор всё равно найдет максимально короткий фрагмент, который позволяет шаблону в целом совпасть. «Ленивость» — не про скорость, а про «наименьшую длину», нужную для успешного сопоставления.
6. Группы, альтернативы и ссылки
Скобочные группы структурируют шаблон и позволяют обращаться к подстрокам:
- (…) — захватывающая группа, нумеруется с 1.
- (?:…) — незахватывающая группа, нужна для структурирования без сохранения подмаски.
- | — альтернация (логическое «или»), приоритет ниже, чем у конкатенации.
- 1, 2 — обратные ссылки на ранее захваченные группы.
- (?P<name>…) — именованная группа; (?P=name) — ссылка по имени.
import re
m = re.search(r"(w+)=([0-9]+)", "size=42")
print(m.group(1), m.group(2)) # size 42
m = re.search(r"<(w+)>.*1>", "bold")
print(bool(m)) # True
m = re.search(r"(?P).*(?P=open)", "text")
print(bool(m)) # True — ссылка по имени
Совет: используйте незахватывающие группы для структурирования, если вы не планируете обращаться к содержимому группы в коде — так findall вернёт сами совпадения, а не кортежи из групп.
7. Просмотры (lookaround)
Просмотры проверяют наличие/отсутствие контекста, но не потребляют символы:
- (?=…) — положительный просмотр вперёд (должно следовать).
- (?!…) — отрицательный просмотр вперёд (не должно следовать).
- (?<=…) — положительный просмотр назад (должно предшествовать).
- (?<!…) — отрицательный просмотр назад (не должно предшествовать).
Ограничение Python: содержимое lookbehind должно быть фиксированной длины. Переменная длина внутри (?<=…) и (?<!…) не поддерживается движком re.
import re
print(bool(re.search(r"(?<=$)d+(?:.d+)?", "Цена $12.50"))) # True — числа после $
print(bool(re.search(r"d+(?=%)", "Рост 12%"))) # True — числа перед %
print(bool(re.search(r"bcatb(?!w)", "cat!"))) # True — после «cat» не слово
8. Флаги компиляции и inline-режимы
- re.I / re.IGNORECASE — регистронезависимый поиск.
- re.M / re.MULTILINE — ^/$ работают на каждую строку.
- re.S / re.DOTALL — точка . совпадает с переводом строки.
- re.X / re.VERBOSE — улучшенный читаемый формат с пробелами и комментариями.
- re.A / re.ASCII — ограничение «буквенно-цифровых» классов до ASCII.
import re
print(re.findall(r"(?im) ^title: (.+) $", "Title: OnenTITLE: Two")) # ['One', 'Two']
pattern = re.compile(r"""
^ # начало строки
(?Pw+) # имя пользователя
@
(?P[w.]+) # хост
$""", re.X | re.I)
print(bool(pattern.fullmatch("[USER@EXAMPLE.com](mailto:USER@EXAMPLE.com)"))) # True
9. API модуля re: когда что использовать
- match — «начало строки». Частая ошибка — ожидать поиска «где угодно».
- search — найдёт первое совпадение в любом месте строки.
- fullmatch — жёсткая проверка формата всей строки.
- findall — быстрый список результатов, удобен в простых случаях.
- finditer — поток совпадений, хорош для больших данных и пост-обработки.
import re
text = "id=17; id=42;"
print(re.findall(r"id=(d+)", text)) # ['17', '42']
for m in re.finditer(r"id=(d+)", text):
print(m.group(1), m.span()) # 17 (3,7), 42 (11,15)
sub принимает строку замены или функцию, которая получает объект Match и возвращает подставляемую строку:
import re
def repl(m):
return str(int(m.group(0)) * 2)
print(re.sub(r"d+", repl, "a1b2c3")) # a2b4c6
10. Сырые строки и экранирование в Python
Чтобы не удваивать обратные косые, используйте сырой литерал строки с префиксом r. Иначе вы рискуете «потерять» часть слэшей ещё на этапе интерпретации Python.
import re
# Плохо: в Python 'd+' — это строка d+; но 'b' и 'b' — уже разные случаи
print(bool(re.search("d+", "A1"))) # True, но читать сложно
# Хорошо:
print(bool(re.search(r"d+", "A1"))) # True
11. Производительность и катастрофический бэктрекинг
Бэктрекинг — механизм возврата и перепробования альтернатив. Некоторые шаблоны приводят к экспоненциальному росту попыток при «почти подходящих» строках. Классический пример — вложенные квантификаторы:
import re, time
pattern = re.compile(r"(a+)+$") # плохой паттерн
s = "a" * 30 + "b" # почти подходит
t = time.time()
print(bool(pattern.search(s)))
print("elapsed:", time.time() - t)
Как снизить риск:
- Избегайте вложенных «широких» квантификаторов.
- Используйте точные классы и якоря вместо «всё подряд» .*.
- Ставьте «узкие» альтернации раньше «широких».
- Валидации делите на этапы: грубая проверка длины и диапазонов до regex.
12. Практикум: готовые рецепты
Email (упрощённая проверка)
import re
p = re.compile(r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$")
print(bool(p.fullmatch("[name.surname+tag@example.co.uk](mailto:name.surname+tag@example.co.uk)")))
Телефон (международный вид, гибко)
import re
p = re.compile(r"^+?d[ds-()]{6,}d$")
print(bool(p.fullmatch("+1 (202) 555-0188")))
Дата «YYYY-MM-DD»
import re
p = re.compile(r"^d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]d|3[01])$")
print(bool(p.fullmatch("2025-11-05")))
IPv4
import re
octet = r"(25[0-5]|2[0-4]d|1?d?d)"
p = re.compile(rf"^{octet}.{octet}.{octet}.{octet}$")
print(bool(p.fullmatch("192.168.0.1")))
Денежная сумма с копейками
import re
p = re.compile(r"^d+(?:.d{2})?$")
print(bool(p.fullmatch("12.50")), bool(p.fullmatch("12")))
Извлечь все числа и их позиции
import re
s = "x=10; y=200; z=3"
for m in re.finditer(r"d+", s):
print(m.group(), m.span())
Логи: уровень и сообщение
import re
line = "2025-11-05 12:00:01 [INFO] Server started"
m = re.search(r"^d{4}-d{2}-d{2}s+d{2}:d{2}:d{2}s+[(w+)]s+(.*)$", line)
print(m.group(1), m.group(2)) # INFO Server started
13. Частые ошибки и анти-паттерны
- Ожидать, что re.match ищет «где угодно». Нет, только с начала строки. Нужен re.search.
- Использовать .* без якорей и контекста. Лучше уточнить границы: классы символов, ленивые квантификаторы, lookaround.
- Забывать про сырые строки r''. Иначе получите ошибки экранирования.
- Неправильно составленные классы и диапазоны. Например, [A-z] включает лишние символы между Z и a; корректно [A-Za-z].
- Слепо верить findall. Если в шаблоне есть группы, findall вернёт кортежи групп, а не целые совпадения. Добавьте (?:...) или используйте finditer.
- Парсить HTML одной регуляркой. Для сложных форматов используйте профильные парсеры, а regex — точечно.
14. Когда регулярки Python не хватает: модуль regex
Сторонний модуль regex (устанавливается через pip) расширяет возможности: поддерживает possessive-квантификаторы, условные проверки, «правильные» пересечения классов, улучшенные свойства Unicode и др. Если вам нужны расширенные функции и строгая производительность, рассмотрите его. Однако имейте в виду различия синтаксиса и поведения при миграции шаблонов между re и regex.
15. Краткая шпаргалка по синтаксису
- Символы: обычные буквы и цифры совпадают сами с собой; спецсимволы экранируйте .
- Классы: [...], [^...], диапазоны a-z; предопределённые d D w W s S.
- Якоря: ^, $, A, Z, границы b, B.
- Квантификаторы: * + ? {m} {m,} {m,n} и ленивые версии с ?.
- Группы: (...), незахватывающие (?:...), именованные (?P<name>...), ссылки 1, (?P=name).
- Просмотры: (?=...), (?!...), (?<=...), (?<!...) (lookbehind — фиксированной длины).
- Флаги: re.I, re.M, re.S, re.X, re.A; inline (?iLmsx).
- API: compile, search, match, fullmatch, findall, finditer, split, sub, escape.
Теперь у вас есть системная картина синтаксиса регулярных выражений в Python и практические шаблоны для типовых задач. При проектировании паттернов начинайте с чётких границ и классов символов, пользуйтесь «сырыми» строками и по возможности избегайте вложенных квантификаторов. Для сложных схем держите под рукой finditer, именованные группы и lookaround — они делают код короче и точнее.
