Python RegEx
Регулярные выражения Python: синтаксис, специальные последовательности, группы, просмотр вперёд, флаги и функции модуля re с понятными примерами.
Регулярные выражения (regex) позволяют искать, извлекать и заменять текст по гибким шаблонам, а не по точным строкам. Встроенный модуль re в Python предоставляет всё необходимое. В этой главе рассматривается полный набор инструментов для работы с regex: синтаксис, специальные последовательности, квантификаторы, группы, просмотр вперёд/назад, флаги и все ключевые функции re — с корректными, рабочими примерами.
Зачем использовать регулярные выражения?
Обычные строковые методы (str.find(), str.replace(), str.split()) хорошо работают с фиксированным текстом. Регулярные выражения незаменимы, когда шаблон может меняться:
- Проверить, выглядит ли строка как адрес электронной почты или номер телефона.
- Извлечь все даты из документа, независимо от точного формата.
- Удалить HTML-теги из строки.
- Заменить несколько различных последовательностей пробелов одним пробелом.
Когда задача связана с описанием формы, а не фиксированного значения, используйте re.
Необработанные строки
Шаблоны regex почти всегда записываются как необработанные строки (r'...'). Необработанные строки обрабатывают обратные слэши как обычные символы, что важно, поскольку regex использует множество последовательностей с обратным слэшем (\d, \w, \s, \b). Без префикса r везде потребовались бы двойные обратные слэши:
import re
# Both patterns are identical — raw string is easier to read
re.findall(r'\d+', 'abc 123') # raw: r'\d+'
re.findall('\\d+', 'abc 123') # normal: '\\d+'Используйте необработанные строки для каждого шаблона regex — это общепринятое соглашение.
Базовый синтаксис: метасимволы
Метасимволы — это символы со специальным значением внутри шаблона. Литеральные символы соответствуют себе в точности.
| Метасимвол | Значение |
|---|---|
. | Любой символ кроме символа новой строки |
^ | Начало строки (или строки текста в режиме MULTILINE) |
$ | Конец строки (или строки текста в режиме MULTILINE) |
* | Ноль или более предшествующих элементов |
+ | Один или более предшествующих элементов |
? | Ноль или один предшествующий элемент |
{n} | Ровно n повторений |
{n,m} | От n до m повторений |
[...] | Класс символов — любой один символ из перечисленных |
[^...] | Отрицательный класс — любой символ, не перечисленный |
| | Чередование — левое или правое выражение |
() | Захватывающая группа |
\ | Экранирование метасимвола или начало специальной последовательности |
Чтобы найти буквальный метасимвол, например . или *, экранируйте его обратным слэшем: \. соответствует настоящей точке.
Специальные последовательности
Специальные последовательности — это сокращённые классы символов, которые часто встречаются в реальных шаблонах.
| Последовательность | Соответствует |
|---|---|
\d | Любая цифра — то же самое, что [0-9] |
\D | Любой нецифровой символ |
\w | Символ слова: буквы, цифры, подчёркивание |
\W | Символ, не являющийся символом слова |
\s | Пробельный символ: пробел, табуляция, новая строка |
\S | Непробельный символ |
\b | Граница слова (нулевой ширины) |
\B | Не-граница слова |
import re
print(re.findall(r'\d+', 'I have 3 cats and 12 dogs'))
# ['3', '12']
print(re.findall(r'\w+', 'hello_world 123'))
# ['hello_world', '123']
print(re.findall(r'\bPython\b', 'Python Pythonista Python3'))
# ['Python'] — word boundary prevents partial matches\b особенно полезна: она соответствует позиции между символом слова и символом-не-словом, поэтому \bPython\b совпадает с отдельным словом «Python», но не с «Pythonista» или «Python3».
Квантификаторы
Квантификаторы управляют тем, сколько раз предшествующий элемент должен совпасть.
import re
print(re.findall(r'a*', 'baaa')) # ['', 'aaa', '']
print(re.findall(r'a+', 'baaa')) # ['aaa']
print(re.findall(r'a?', 'baaa')) # ['', 'a', 'a', 'a', '']
print(re.findall(r'a{3}', 'baaa')) # ['aaa']Жадное и ленивое совпадение
По умолчанию квантификаторы жадные — они совпадают с как можно большим количеством текста. Добавьте ? после квантификатора, чтобы сделать его ленивым (совпадать с как можно меньшим количеством).
import re
html = '<b>bold</b> and <i>italic</i>'
print(re.findall(r'<.*>', html))
# ['<b>bold</b> and <i>italic</i>'] — greedy: matches from first < to last >
print(re.findall(r'<.*?>', html))
# ['<b>', '</b>', '<i>', '</i>'] — lazy: matches each individual tagЛенивые квантификаторы незаменимы при разборе структурированного текста, например HTML или фрагментов JSON.
Классы символов
Класс символов [...] совпадает с любым одним символом из указанного набора. Используйте - для диапазонов и ^ в начале для отрицания класса.
import re
print(re.findall(r'[aeiou]', 'hello world'))
# ['e', 'o', 'o']
print(re.findall(r'[0-9]', 'a1b2c3'))
# ['1', '2', '3']
print(re.findall(r'[^aeiou\s]+', 'hello world'))
# ['h', 'll', 'w', 'rld'] — consonants only (not vowels, not spaces)Распространённые готовые диапазоны: [a-z] строчные буквы, [A-Z] прописные, [0-9] цифры, [a-zA-Z0-9] буквенно-цифровые символы.
Якоря
Якоря не поглощают символы — они указывают на позицию в строке.
import re
print(re.findall(r'^Python', 'Python is great'))
# ['Python'] — matches only if 'Python' is at the start
print(re.findall(r'great$', 'Python is great'))
# ['great'] — matches only if 'great' is at the end
print(re.findall(r'^Python', 'Learn Python'))
# [] — 'Python' is not at the start of this stringСмотрите флаг re.MULTILINE далее в этой главе, чтобы применять ^ и $ к каждой строке текста, а не ко всей строке целиком.
Чередование
Символ | работает как логическое ИЛИ между двумя выражениями.
import re
print(re.findall(r'cat|dog', 'I have a cat and a dog'))
# ['cat', 'dog']
print(re.findall(r'colou?r|colour', 'color and colour'))
# ['color', 'colour']Захватывающие группы
Скобки () создают захватывающую группу. Группы позволяют извлекать подчасти совпадения. re.search() возвращает объект совпадения; вызовите .group(n) или .groups() на нём.
import re
match = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2023-10-05')
if match:
print(match.group(0)) # '2023-10-05' — entire match
print(match.group(1)) # '2023'
print(match.groups()) # ('2023', '10', '05')Именованные группы
Дайте группе имя с помощью (?P<name>...), чтобы обращаться к ней по имени, а не по позиции. Это делает шаблоны значительно проще в поддержке.
import re
match = re.search(
r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
'2023-10-05'
)
if match:
print(match.group('year')) # '2023'
print(match.groupdict()) # {'year': '2023', 'month': '10', 'day': '05'}Незахватывающие группы
Используйте (?:...), когда нужно сгруппировать, но не нужно захватывать значение.
import re
print(re.findall(r'(?:Mr|Mrs|Ms)\.? \w+', 'Mr. Smith and Mrs. Jones'))
# ['Mr. Smith', 'Mrs. Jones']Просмотр вперёд и просмотр назад
Просмотр вперёд ((?=...)) и просмотр назад ((?<=...)) проверяют, что за совпадением следует или предшествует что-то, не включая это в результат. Они имеют нулевую ширину: не поглощают символы.
import re
# Positive lookahead — find numbers followed by 'dollars'
print(re.findall(r'\d+(?= dollars)', '100 dollars and 200 euros'))
# ['100']
# Negative lookahead — find numbers NOT followed by 'dollars'
print(re.findall(r'\b\d+\b(?! dollars)', '100 dollars and 200 euros'))
# ['200']
# Positive lookbehind — extract domain from email addresses
emails = 'Contact [email protected] or [email protected]'
print(re.findall(r'(?<=@)\w+\.\w+', emails))
# ['example.com', 'test.org']Шаблоны просмотра назад в Python должны иметь фиксированную ширину — внутри них нельзя использовать * или +.
Модуль re: основные функции
re.search() — найти первое совпадение
Возвращает объект совпадения для первой позиции, где шаблон совпадает, или None, если совпадений нет.
import re
text = 'apple banana apple'
match = re.search(r'banana', text)
if match:
print(match.group()) # 'banana'
print(match.span()) # (6, 12)re.match() — совпадение только в начале
Как re.search(), но шаблон должен совпасть в начале строки.
import re
print(bool(re.match(r'\d+', 'abc123'))) # False — no digit at start
print(bool(re.search(r'\d+', 'abc123'))) # True — digit found anywhereИспользуйте re.search(), когда нужно найти шаблон в любом месте; используйте re.match(), когда шаблон должен находиться в начале.
re.fullmatch() — совпадение со всей строкой
Шаблон должен совпасть со всей строкой от начала до конца.
import re
print(bool(re.fullmatch(r'\d{5}', '12345'))) # True — exactly 5 digits
print(bool(re.fullmatch(r'\d{5}', '123456'))) # False — too long
print(bool(re.fullmatch(r'\d{5}', '1234X'))) # False — non-digit presentre.fullmatch() идеально подходит для проверки входных данных (почтовые индексы, номера телефонов и т. д.).
re.findall() — все непересекающиеся совпадения
Возвращает список всех совпадений. Если шаблон содержит группы, возвращает список кортежей.
import re
print(re.findall(r'\d+', 'abc 123 def 456'))
# ['123', '456']
# With a group — returns list of group values
print(re.findall(r'(\w+)@(\w+\.\w+)', '[email protected] [email protected]'))
# [('alice', 'example.com'), ('bob', 'test.org')]re.finditer() — итератор объектов совпадений
Как re.findall(), но возвращает объекты совпадений по одному. Полезно для больших текстов или когда нужна информация о позициях.
import re
for m in re.finditer(r'\d+', 'abc 123 def 456'):
print(m.group(), m.start(), m.end())
# 123 4 7
# 456 12 15re.sub() — замена совпадений
Заменяет каждое вхождение шаблона строкой замены или возвращаемым значением функции.
import re
text = 'apple banana apple'
print(re.sub(r'apple', 'orange', text))
# 'orange banana orange'
# Limit replacements
print(re.sub(r'apple', 'orange', text, count=1))
# 'orange banana apple'
# Backreferences in replacement — reformat a date
print(re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', '2023-10-05'))
# '05/10/2023're.split() — разделение по шаблону
Разделяет строку при каждом вхождении шаблона.
import re
print(re.split(r',\s*', 'apple, banana, cherry, date'))
# ['apple', 'banana', 'cherry', 'date']
# Limit the number of splits
print(re.split(r',\s*', 'apple, banana, cherry, date', maxsplit=2))
# ['apple', 'banana', 'cherry, date']Компиляция шаблонов с re.compile()
Когда один и тот же шаблон используется многократно, скомпилируйте его один раз с помощью re.compile() для повышения производительности. Полученный объект шаблона имеет все те же методы (findall, search, sub и т. д.).
import re
# Compile once
digit_pattern = re.compile(r'\d+')
# Reuse many times
print(digit_pattern.findall('abc 123 def 456')) # ['123', '456']
print(digit_pattern.sub('NUM', 'abc 123 def 456')) # 'abc NUM def NUM'
print(digit_pattern.search('xyz 99')) # <re.Match object ...>Компиляция особенно ценна внутри циклов или часто вызываемых функций.
Флаги
Флаги изменяют поведение сопоставления. Передайте их последним аргументом любой функции re или при вызове re.compile(). Несколько флагов можно объединить с помощью |.
| Флаг | Краткая форма | Эффект |
|---|---|---|
re.IGNORECASE | re.I | Сопоставление без учёта регистра |
re.MULTILINE | re.M | ^ и $ совпадают с началом/концом каждой строки текста |
re.DOTALL | re.S | . совпадает также с символами новой строки |
re.VERBOSE | re.X | Разрешает пробелы и комментарии внутри шаблона |
import re
# IGNORECASE
print(re.findall(r'hello', 'Hello HELLO hello', re.IGNORECASE))
# ['Hello', 'HELLO', 'hello']
# MULTILINE — ^ matches the start of each line
text = 'first line\nsecond line\nthird line'
print(re.findall(r'^\w+', text, re.MULTILINE))
# ['first', 'second', 'third']
# DOTALL — . matches newline characters
print(re.findall(r'<.*?>', '<div>\n<p>text</p>\n</div>', re.DOTALL))
# ['<div>', '<p>', '</p>', '</div>']
# VERBOSE — write readable patterns with comments
date_pattern = re.compile(r'''
(?P<year>\d{4}) # four-digit year
-
(?P<month>\d{2}) # two-digit month
-
(?P<day>\d{2}) # two-digit day
''', re.VERBOSE)
print(date_pattern.search('2023-10-05').groupdict())
# {'year': '2023', 'month': '10', 'day': '05'}Экранирование пользовательского ввода с re.escape()
Если вы строите шаблон из текста, введённого пользователем, всегда сначала экранируйте его, чтобы предотвратить непреднамеренную интерпретацию метасимволов.
import re
user_input = 'hello.world'
# Without escaping, '.' matches any character
# With escaping, '\.' matches a literal dot
safe_pattern = re.escape(user_input)
print(safe_pattern) # 'hello\\.world'
print(bool(re.search(safe_pattern, 'say hello.world'))) # True
print(bool(re.search(safe_pattern, 'say helloXworld'))) # FalseПрактические примеры
Проверка адреса электронной почты
import re
def is_valid_email(email):
pattern = r'^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$'
return bool(re.fullmatch(pattern, email))
print(is_valid_email('[email protected]')) # True
print(is_valid_email('bad-email@')) # FalseИзвлечение всех URL из текста
import re
text = 'Visit https://www.example.com or http://test.org for details.'
urls = re.findall(r'https?://[\w./-]+', text)
print(urls)
# ['https://www.example.com', 'http://test.org']Удаление лишних пробелов
import re
messy = ' hello world Python '
clean = re.sub(r'\s+', ' ', messy).strip()
print(clean) # 'hello world Python'Разбор строки журнала
import re
log = '2023-10-05 14:32:11 ERROR Failed to connect to database'
pattern = re.compile(
r'(?P<date>\d{4}-\d{2}-\d{2}) '
r'(?P<time>\d{2}:\d{2}:\d{2}) '
r'(?P<level>\w+) '
r'(?P<message>.+)'
)
m = pattern.match(log)
if m:
print(m.group('level')) # 'ERROR'
print(m.group('message')) # 'Failed to connect to database'Типичные ошибки
re.match() против re.search() — re.match() проверяет только начало строки. Новые пользователи часто ожидают, что он ищет в любом месте, и удивляются, когда получают None.
Забытые необработанные строки — '\d' в обычной строке Python — это просто 'd' с нераспознанным escape-символом (или SyntaxWarning в Python 3.12+). Всегда пишите r'\d'.
Жадные совпадения, захватывающие лишнее — если шаблон .* захватывает больше, чем нужно, переключитесь на .*? (ленивый).
Специальные символы в строках замены — в re.sub() последовательности с обратным слэшем, такие как \1 в замене, ссылаются на захваченные группы. Чтобы включить буквальный обратный слэш в замену, напишите \\.
re.findall() с группами — когда шаблон содержит группы, re.findall() возвращает группы, а не полное совпадение. Используйте незахватывающую группу (?:...), если нужно полное совпадение.
Краткая справка
| Задача | Функция |
|---|---|
| Найти первое совпадение | re.search(pattern, text) |
| Найти все совпадения | re.findall(pattern, text) |
| Перебрать совпадения | re.finditer(pattern, text) |
| Совпадение в начале | re.match(pattern, text) |
| Совпадение со всей строкой | re.fullmatch(pattern, text) |
| Замена | re.sub(pattern, repl, text) |
| Разделение | re.split(pattern, text) |
| Компиляция для повторного использования | re.compile(pattern) |
| Экранирование пользовательского ввода | re.escape(text) |
Связанные главы
- Python Strings — строковые методы, дополняющие regex для более простых текстовых задач.
- Modify Strings — встроенные методы, такие как
replace()иsplit(), которые позволяют обойтись без regex при фиксированных шаблонах. - Python File Handling — чтение файлов построчно для применения regex в большом масштабе.
- Python Try/Except — обработка ошибок, возникающих при компиляции шаблонов regex из пользовательского ввода.
- Python Functions — оборачивание логики regex в повторно используемые функции.