
Синтаксис регулярных выражений в Python
Практическое руководство по регулярным выражениям в Python: ключевые классы символов (\d \w \s . [] [^]), якоря ^ $ \b \B, квантификаторы жадные и ленивые (* + ? {m,n}), группы захвата и именованные, альтернация |, просмотры вперёд/назад, флаги модуля re, компиляция паттернов, различия match/search/fullmatch, извлечение и замены, особенности Unicode и частые ошибки. Весь текст в одну строку, многострочные примеры кода — только внутри блоков кода.
Введение и модуль re
Регулярные выражения — язык поиска по шаблону. В Python основу даёт модуль re: он ищет подстроки, валидирует формат данных и разбивает строки. Используйте «сырые» строки r"…", чтобы обратные слэши не обрабатывались интерпретатором.
import re
m = re.search(r"cat|dog", "hotdog")
print(bool(m)), print(m.group())
# True
# dog
Подготовка: сырой литерал и базовые функции
Сырые строки r"...” избавляют от двойного экранирования. Базовые вызовы: re.search — ищет где угодно; re.match — только с начала; re.fullmatch — должна совпасть вся строка.
import re
print(re.search(r"\d+", "id=42").group()) # '42'
print(re.match(r"\d+", "42px").group()) # '42'
print(re.fullmatch(r"\d+", "12345") is not None) # True
print(re.fullmatch(r"\d+", "123a") is not None) # False
Классы символов и наборы
\d — цифра, \w — буква/цифра/подчёркивание, \s — пробельный; заглавные — отрицания (\D \W \S). Наборы [abc], диапазоны [a-z], отрицание [^…]. Точка . — «любой символ, кроме перевода строки» без флага DOTALL.
import re
print(re.findall(r"\w+", "Привет, мир_2025!")) # ['Привет', 'мир_2025']
print(re.findall(r"[A-Fa-f0-9]{2}", "ff:0A:19")) # ['ff', '0A', '19']
print(re.findall(r"[^, ]+", "a, b, c")) # ['a', 'b', 'c']
Якоря и границы слова
^ — начало строки, $ — конец; в многострочном режиме MULTILINE — начало/конец каждой строки. \b — граница слова, \B — неграница.
import re
text = "start\nmid word\nend"
print(re.findall(r"^\w+", text, flags=re.M)) # ['start', 'mid', 'end']
print(re.findall(r"\bcat\b", "black cat, scatter")) # ['cat']
print(re.findall(r"\Bcat\B", "concatenate")) # ['cat']
Квантификаторы: жадные и ленивые
Жадные: * — 0+, + — 1+, ? — 0/1, {m,n} — диапазон. Ленивая версия добавляет ?: *?, +?, ??, {m,n}?.
import re
html = "boldtwo"
print(re.findall(r".*", html)) # ['boldtwo'] жадно
print(re.findall(r".*?", html)) # ['bold', 'two'] лениво
print(re.findall(r"\d{2,4}", "7 12 2024")) # ['12', '2024']
Группы и ссылки
Захватывающие скобки (…) сохраняют подстроки. Незахватывающие (?:…) группируют без сохранения. Именованные группы (?P<name>…). Ссылки: \1 и \g<name>.
import re
m = re.search(r"(\w+)-(\d+)", "user-007")
print(m.group(1), m.group(2)) # user 007
print(re.findall(r"(?:ab)+", "ababa abb ababab")) # ['abab', 'ababab']
m = re.search(r"(?P\w+)\s+(?P\d+)", "alice 42")
print(m.group("name"), m.group("id")) # alice 42
print(re.sub(r"(\w+)-(\d+)", r"\2-\1", "user-007")) # 007-user
print(re.sub(r"(?P\w+)-(?P\d+)", r"\g-\g", "user-007")) # 007-user
Альтернация и приоритеты
| — «или». Приоритет ниже, чем у конкатенации и квантификаторов, поэтому группируйте скобками.
import re
print(re.findall(r"gr(a|e)y", "gray grey gry")) # ['a', 'e']
print(bool(re.search(r"(cat|dog)s?", "dogs"))) # True
Просмотры вперёд и назад
Просмотры проверяют контекст без потребления символов: позитивный вперёд (?=…), негативный (?!…), позитивный назад (?<=…), негативный назад (?<!…).
import re
s = "price: 10$, 20USD, 30 €"
print(re.findall(r"\d+(?=$)", s)) # ['10']
print(re.findall(r"\d+(?!USD)", s)) # ['10', '30']
print(re.findall(r"(?<=: )\d+", s)) # ['10']
print(re.findall(r"(?
Флаги re и inline-режимы
IGNORECASE — регистронезависимо, MULTILINE — ^/$ по строкам, DOTALL — точка захватывает перевод строки, VERBOSE — читаемые паттерны с комментариями, ASCII — классы символов ведут себя по ASCII. Флаги задаются параметром flags= или инлайн (?imxs).
import re
text = "A\nb"
print(re.findall(r".", text)) # ['A', '\n', 'b']
print(re.findall(r".", text, flags=re.S)) # ['A', '\n', 'b']
pat = re.compile(r"""
^\s* # начало строки, пропуск пробелов
(\w+) # ключ
\s*=\s* # равно
(.+) # значение
$ # конец строки
""", flags=re.X | re.M)
print(bool(pat.search("name = Alice")))
Методы поиска: match, search, fullmatch
match — с начала строки, search — в любом месте, fullmatch — вся строка. Для валидации формата используйте fullmatch.
import re
print(re.match(r"\d+", "abc123") is None) # True
print(re.search(r"\d+", "abc123").group()) # '123'
print(re.fullmatch(r"[A-Z]{2}\d{3}", "AB123") is not None) # True
findall против finditer
findall возвращает список совпадений, finditer — генератор объектов Match, экономит память и даёт позиции.
import re
text = "a1 b22 c333"
print(re.findall(r"\d+", text)) # ['1', '22', '333']
for m in re.finditer(r"\d+", text):
print(m.group(), m.span())
# 1 (1, 2)
# 22 (4, 6)
# 333 (8, 11)
Замены и split с обратными ссылками
sub заменяет, subn возвращает пару (строка, число замен), split разбивает по паттерну. В подстановке используйте \1, \g<name> или функцию.
import re
s = "color colour"
print(re.sub(r"colou?r", "COLOR", s)) # 'COLOR COLOR'
print(re.subn(r"\s+", "-", "a b c")) # ('a-b-c', 2)
print(re.split(r"[;,\s]+", "a, b; c d")) # ['a', 'b', 'c', 'd']
def up(m: re.Match) -> str:
return m.group(1).upper()
print(re.sub(r"(\w+)", up, "one two")) # 'ONE TWO'
Компиляция паттернов и объект Pattern
re.compile создаёт объект паттерна для повторного использования и снижает накладные расходы. Это полезно в циклах, обработчиках и при нескольких операциях с одним шаблоном.
import re
pat = re.compile(r"\b\w{3,}\b")
data = ["go", "read", "map", "alpha", "xy"]
print([p for s in data if (m := pat.search(s)) for p in [m.group()]])
# ['read', 'map', 'alpha']
Match-объект: группы и позиции
Доступ к результатам: group(), groups(), groupdict(); позиции — start/end/span.
import re
m = re.search(r"(?P\w+)=(?P.+)", "lang=Python3")
print(m.group(), m.group(1), m.group("val")) # 'lang=Python3' 'lang' 'Python3'
print(m.groups()) # ('lang', 'Python3')
print(m.groupdict()) # {'key': 'lang', 'val': 'Python3'}
print(m.start(), m.end(), m.span()) # 0 12 (0, 12)
Unicode и нормализация
По умолчанию классы учитывают Unicode: \w включает буквы разных алфавитов. Флаг ASCII ограничивает до ASCII. Для составных символов используйте нормализацию.
import re, unicodedata as ud
print(re.findall(r"\w+", "тест test テスト")) # ['тест', 'test', 'テスト']
print(re.findall(r"\w+", "тест test", flags=re.A)) # ['test']
s1 = "e\u0301" # 'e' + COMBINING ACUTE
s2 = "é"
print(bool(re.fullmatch(r".", s1))) # False без нормализации
print(ud.normalize("NFC", s1) == s2) # True
Практические шаблоны: числа, даты, e-mail, IPv4
Ниже — компактные и понятные варианты популярных паттернов. В реальных проектах адаптируйте под доменные правила.
import re
# Число с опциональной дробной частью
num = re.compile(r"[+-]?(?:\d+(?:.\d+)?|.\d+)")
print(num.findall("x=-3.14; y=.5; z=10")) # ['-3.14', '.5', '10']
# Дата ISO YYYY-MM-DD
date = re.compile(r"(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])")
print(bool(date.fullmatch("2025-11-05")))
# Приземлённый e-mail (упрощённый)
email = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}")
print(email.findall("[a@b.io](mailto:a@b.io), bad@host, [ok@mail.com](mailto:ok@mail.com)"))
# IPv4
ipv4 = re.compile(r"(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3}")
print(bool(ipv4.fullmatch("192.168.1.100")))
Валидация против парсинга
Regex уместен для проверки формата и фильтрации. Для вложенных структур, балансирующих скобок и контекстно-зависимых правил используйте парсеры.
Лучшие практики и тестирование
Пишите читаемо с re.VERBOSE, компилируйте многократно используемые шаблоны, ограничивайте жадность и добавляйте якоря для ускорения. Тестируйте позитивные/негативные случаи и границы.
import re
pat = re.compile(r"""
^ # начало
(?:\+7|8)\s? # префикс
\(?\d{3}\)?\s? # код
\d{3}[- ]?\d{2}[- ]?\d{2}
$ # конец
""", re.X)
tests = ["+7 (495) 123-45-67", "8 4951234567", "8495-123-45-67", "7 495 1234567"]
print([bool(pat.fullmatch(t)) for t in tests])
Частые ошибки и ловушки
Забытое экранирование спецсимволов (., [], (), {}, ?, *, +, |, ^, $), «сломанные» диапазоны ([A-z] включает лишние символы), лишняя жадность (.*), использование match вместо fullmatch для валидации, отсутствие r"" и как следствие неверные \b/\t.
import re
print(bool(re.search(r"[A-z]", "_"))) # True из-за диапазона между 'Z' и 'a'
print(bool(re.search(r"[A-Za-z]", "_"))) # False — корректный диапазон
Мини-практикум: 8 задач
Короткие упражнения для закрепления синтаксиса.
import re
# 1) Слова длиной 4+
print(re.findall(r"\b\w{4,}\b", "a bb cool regex power"))
# 2) Домены из URL
print(re.findall(r"https?://([^/]+)/?", "[https://ex.com/a](https://ex.com/a) [http://site.org](http://site.org)"))
# 3) Поменять местами Фамилию и Имя
print(re.sub(r"(\w+)\s+(\w+)", r"\2 \1", "Ivan Petrov"))
# 4) Числа перед 'USD'
print(re.findall(r"\d+(?=\s*USD)", "10 USD, 20USD, 30 EUR"))
# 5) Только строки вида 'key=value'
lines = "a=1\nb:2\nc = 3"
print(re.findall(r"^\s*\w+\s*=\s*\S+\s*$", lines, flags=re.M))
# 6) Пары (ключ, значение)
m = re.finditer(r"(?P\w+)\s*=\s*(?P\S+)", lines)
print([(i.group('k'), i.group('v')) for i in m])
# 7) Разбивка по ; , или пробелам
print(re.split(r"[;,\s]+", "a, b; c d"))
# 8) Пароль: минимум 8, буква, цифра
pwd = re.compile(r"^(?=.*[A-Za-z])(?=.*\d).{8,}$")
print([bool(pwd.fullmatch(p)) for p in ["abc12345", "abcdef", "12345678"]])
FAQ и итог
match или search? Для проверки формата — fullmatch, для нахождения подстроки — search. Как сделать «ленивым»? Добавьте ? к квантификатору: *?, +?, {m,n}?. Группы против просмотров? Группы возвращают данные, просмотры только проверяют контекст. Чек-лист: выбраны подходящие классы символов, добавлены якоря, жадность ограничена, паттерн разбит и документирован в re.VERBOSE, есть позитивные и негативные тесты.