Перейти к содержимому
Главная страница

Синтаксис регулярных выражений в Python: понятное руководство с примерами

Логотип языка программирования Python на синем фоне с надписью «PYTHON»

Регулярные выражения

Регулярные выражения в 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 = "onetwo"
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+)>.*", "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 — они делают код короче и точнее.

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

Достигнут лимит времени. Пожалуйста, введите CAPTCHA снова.

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии