Основы и продвинутые возможности регулярных выражений в Python
Регулярные выражения (regex) представляют собой мощный инструмент для поиска, замены и анализа текстовых данных. В Python вся функциональность сосредоточена в модуле re. Основной подход заключается в компиляции шаблона с помощью re.compile() для последующего многократного использования, что повышает производительность. Однако для однократных операций удобнее применять функции re.search(), re.match(), re.findall(), re.sub() и re.split().
Основное решение: компиляция шаблона и методы объекта Pattern
Наиболее эффективным решением для многократного применения одного и того же шаблона является его предварительная компиляция. Это ускоряет работу, особенно внутри циклов.
import re
# Компиляция шаблона
pattern = re.compile(r'\d+')
text = 'Цена 200 рублей, скидка 50'
matches = pattern.findall(text)
print(matches) # ['200', '50']Python регулярные (регулярные выражения в python)
Здесь re.compile() создаёт объект Pattern, у которого доступны те же методы (search, match, findall, sub, split). Результат вызова findall возвращает все непересекающиеся подстроки, соответствующие шаблону.
Типичные ошибки: забыть указать сырую строку (r'') – тогда обратные слеши будут интерпретироваться как escape-последовательности Python. Например, '\d' станет просто символом 'd'. Правильно: r'\d'. Ещё одна частая проблема – использование re.match() вместо re.search(), когда нужно найти шаблон не только в начале строки.
Варианты решения
Как найти подстроку в любом месте строки?
Функция re.search() ищет первое совпадение во всей строке. В отличие от re.match(), который проверяет только начало.
import re
text = 'Привет, мир!'
result = re.search(r'мир', text)
if result:
print('Найдено:', result.group()) # 'мир'Проблема: если шаблон не найден, возвращается None. Всегда проверяйте результат перед вызовом .group().
Как извлечь все совпадения по шаблону?
re.findall() возвращает список всех подстрок или кортежей групп, если шаблон содержит группы.
import re
text = 'телефоны: 123-45-67, 987-65-43'
phones = re.findall(r'\d{3}-\d{2}-\d{2}', text)
print(phones) # ['123-45-67', '987-65-43']Особенность: если шаблон содержит группы, findall возвращает только группы. Чтобы получить полное совпадение, нужно обернуть всю маску в группу без захвата или использовать finditer.
Как заменить фрагменты текста по шаблону?
Функция re.sub() заменяет все непересекающиеся совпадения на заданную строку. Можно использовать обратные ссылки.
import re
text = 'год 2023, год 2024'
new_text = re.sub(r'\d{4}', 'XXXX', text)
print(new_text) # 'год XXXX, год XXXX'Ошибка: если шаблон может пересекаться, будут заменены только непересекающиеся совпадения. Для более сложной логики применяйте пользовательскую функцию в качестве замены.
Как разделить строку по сложному разделителю?
re.split() принимает шаблон вместо фиксированного разделителя, что позволяет гибко делить строку.
import re
text = 'яблоко, груша; банан вишня'
parts = re.split(r'[,;\s]+', text)
print(parts) # ['яблоко', 'груша', 'банан', 'вишня']Нюанс: при использовании групп в шаблоне, разделители также попадают в результат. Чтобы избежать этого, используйте незахватывающие группы или устанавливайте maxsplit.
Как изменить поведение поиска: игнорировать регистр или многострочность?
Флаги передаются как дополнительный аргумент. re.IGNORECASE, re.MULTILINE, re.DOTALL и т.д.
import re
text = 'Первый\nВторой'
# re.MULTILINE заставляет ^ и $ работать с каждой строкой
result = re.search(r'^Второй', text, re.MULTILINE)
print(result.group() if result else 'нет') # 'Второй'Проблема: комбинирование флагов через re.X | re.I (побитовое ИЛИ). Порядок не важен.
Как извлечь часть совпадения с помощью групп?
Круглые скобки создают захватывающие группы. Доступ к ним через .group() или .groups().
import re
text = 'Имя: Иван, возраст: 25'
match = re.search(r'Имя: (\w+), возраст: (\d+)', text)
if match:
name, age = match.groups()
print(f'Имя: {name}, возраст: {age}')Ошибка: если внутри группы есть нежадный квантификатор, может быть найдена пустая строка. Следует тщательно конструировать шаблон.
Расширенные примеры использования регулярных выражений
Здесь рассмотрены более сложные конструкции, которые часто требуются при обработке текста.
Lookahead и lookbehind (опережающие и ретроспективные проверки)
Позволяют накладывать условия на контекст без включения его в совпадение.
import re
text = 'цена 100 руб, стоимость 50 USD, 200 рублей'
# Найти числа, после которых идёт 'руб' (без включения 'руб' в результат)
pattern = re.compile(r'\d+(?=\s*руб)')
matches = pattern.findall(text)
print(matches) # ['100', '200']Результат: ['100', '200']
Отрицательный просмотр вперёд: (?!...) – не должно следовать.
import re
# Найти числа, после которых не идёт 'USD'
pattern = re.compile(r'\d+(?!\s*USD)')
print(pattern.findall(text)) # ['100', '200', '200'] (но '50' не входит)Результат: ['100', '200'] (первое '200' из '200 рублей', второе из '200рублей'? нужно уточнить)
Внимание: lookbehind требует фиксированной длины. В Python модуль re поддерживает только фиксированную длину. Для переменной длины нужно использовать regex (сторонняя библиотека).
Именованные группы
Облегчают чтение и поддержку сложных шаблонов.
import re
text = 'Дата: 15-03-2024'
pattern = re.compile(r'(?P<day>\d{2})-(?P<month>\d{2})-(?P<year>\d{4})')
match = pattern.search(text)
if match:
print(match.group('day'), match.group('month'), match.group('year'))
# 15 03 2024Вербозный режим (re.VERBOSE)
Позволяет писать шаблоны с комментариями и переносами строк.
import re
pattern = re.compile(r'''
\d{1,3} # первые три цифры
(?:-)\d{2} # дефис и две цифры
(?:-)\d{2} # дефис и последние две цифры
''', re.VERBOSE)
text = 'тел: 123-45-67'
match = pattern.search(text)
print(match.group() if match else 'нет') # 123-45-67Валидация email с помощью fullmatch
re.fullmatch() требует, чтобы вся строка полностью соответствовала шаблону.
import re
email_pattern = re.compile(r'^[\w.-]+@[\w.-]+\.\w{2,}$')
email = 'user@example.com'
if email_pattern.fullmatch(email):
print('Email корректен')
else:
print('Некорректный email')Замена с использованием групп в re.sub
Можно ссылаться на группы в строке замены через \1, \g<имя>.
import re
text = 'Иванов Иван'
# Меняем фамилию и имя местами
new_text = re.sub(r'(\w+)\s(\w+)', r'\2 \1', text)
print(new_text) # 'Иван Иванов'Парсинг логов с извлечением IP-адресов
import re
log = '192.168.1.1 - - [10/Oct/2023:13:55:36] "GET /index.html" 200 2326'
ip_pattern = re.compile(r'\d{1,3}(\.\d{1,3}){3}')
ip = ip_pattern.search(log).group()
print('IP:', ip) # 192.168.1.1Сложность: для точного определения форматов (email, IP) лучше использовать специализированные библиотеки, так как регулярные выражения могут не покрыть все граничные случаи.